WEBVTT

1
00:00:00.000 --> 00:00:02.720
Hey everyone, I'm Michael

2
00:00:02.720 --> 00:00:04.480
and I'm going to be talking to you today

3
00:00:04.480 --> 00:00:07.640
about asynchronous programming in Emacs Lisp.

4
00:00:07.640 --> 00:00:10.360
I'm located in the San Francisco Bay Area

5
00:00:10.360 --> 00:00:12.040
where I'm a developer as well as

6
00:00:12.040 --> 00:00:14.160
a long time Emacs user.

7
00:00:14.160 --> 00:00:18.760
You may have heard of async or asynchronous programming.

8
00:00:18.760 --> 00:00:21.360
The idea has been around for decades

9
00:00:21.360 --> 00:00:24.400
but it first gained widespread attention in JavaScript

10
00:00:24.400 --> 00:00:26.720
back in the aughts.

11
00:00:26.720 --> 00:00:29.680
Then in the teens it gained tremendous popularity

12
00:00:29.680 --> 00:00:31.720
in the DevOps world with Golang.

13
00:00:31.720 --> 00:00:33.800
And just in the last few years

14
00:00:33.800 --> 00:00:37.880
support for async programming has landed in Rust.

15
00:00:37.880 --> 00:00:40.080
Well it can be done in Emacs as well

16
00:00:40.080 --> 00:00:42.040
and this talk will demonstrate that

17
00:00:42.040 --> 00:00:44.600
by walking you through a little problem

18
00:00:44.600 --> 00:00:47.200
that I actually solved for myself.

19
00:00:47.200 --> 00:00:49.040
Like a lot of these stories

20
00:00:49.040 --> 00:00:51.920
it begins with scratching a personal itch.

21
00:00:51.920 --> 00:00:55.320
In my case automating my music server.

22
00:00:55.320 --> 00:00:58.240
I use something called the music player daemon locally

23
00:00:58.240 --> 00:01:00.240
and as the name suggests

24
00:01:00.240 --> 00:01:03.560
it just kind of hangs out in the background.

25
00:01:03.560 --> 00:01:08.040
Reads music files and talks to assorted sound drivers.

26
00:01:08.040 --> 00:01:09.640
In fact it is so focused on

27
00:01:09.640 --> 00:01:12.440
that mission that it doesn't even offer a user interface.

28
00:01:12.440 --> 00:01:14.400
Instead it serves an API

29
00:01:14.400 --> 00:01:16.120
and invites application developers

30
00:01:16.120 --> 00:01:19.360
to build clients on top of that API.

31
00:01:19.360 --> 00:01:22.200
Okay so let's hop into a vterm

32
00:01:22.200 --> 00:01:25.080
and I'd like to show you the MPD client I use

33
00:01:25.080 --> 00:01:26.600
for my daily driver.

34
00:01:26.600 --> 00:01:29.520
Something called ncmpcpp.

35
00:01:29.520 --> 00:01:31.800
Doesn't exactly roll off the tongue

36
00:01:31.800 --> 00:01:33.720
but I've got a playlist.

37
00:01:33.720 --> 00:01:36.560
I can browse the file system.

38
00:01:36.560 --> 00:01:39.240
Looks like I can search my music library.

39
00:01:39.240 --> 00:01:40.000
Yada yada yada.

40
00:01:40.000 --> 00:01:42.600
It's got all the basic features.

41
00:01:42.600 --> 00:01:44.640
The point that I want to make is that

42
00:01:44.640 --> 00:01:51.920
ncmpcpp is a completely independent project of MPD.

43
00:01:51.920 --> 00:01:53.720
Separate and distinct.

44
00:01:53.720 --> 00:01:55.680
It does all of its work

45
00:01:55.680 --> 00:01:57.200
by simply communicating with

46
00:01:57.200 --> 00:02:01.400
the music player daemon over the API.

47
00:02:01.400 --> 00:02:03.440
Well I wanted to program to that API

48
00:02:03.440 --> 00:02:05.840
only from within Emacs.

49
00:02:05.840 --> 00:02:09.520
Now there are already Emacs MPD clients out there

50
00:02:09.520 --> 00:02:11.560
but I didn't really want a full blown client.

51
00:02:11.560 --> 00:02:14.320
I just wanted a few small tweaks

52
00:02:14.320 --> 00:02:16.320
over my current configuration.

53
00:02:16.320 --> 00:02:19.280
A command to skip to the next song.

54
00:02:19.280 --> 00:02:22.360
Maybe shove the current track into the mode line.

55
00:02:22.360 --> 00:02:24.160
Things like this.

56
00:02:24.160 --> 00:02:28.560
I needed an elisp API that would let me do this.

57
00:02:28.560 --> 00:02:32.000
Okay well let's get out of ncmpcpp

58
00:02:32.000 --> 00:02:37.560
and let's get into a netcat session

59
00:02:37.560 --> 00:02:39.400
with my local MPD server.

60
00:02:39.400 --> 00:02:43.840
As you can see we get a welcome string.

61
00:02:43.840 --> 00:02:46.800
So it is a server goes first protocol.

62
00:02:46.800 --> 00:02:49.640
But after that it's a very familiar

63
00:02:49.640 --> 00:02:53.960
text based request response oriented protocol.

64
00:02:53.960 --> 00:02:56.240
I can ask for the volume.

65
00:02:56.240 --> 00:02:58.160
I can ask for the status.

66
00:02:58.160 --> 00:03:06.000
But in particular I wanted an asynchronous API.

67
00:03:06.000 --> 00:03:07.800
If I issue a command like

68
00:03:07.800 --> 00:03:11.840
find every track in my library

69
00:03:11.840 --> 00:03:15.360
that's going to produce a lot of data

70
00:03:15.360 --> 00:03:18.920
that's a human perceptible pause

71
00:03:18.920 --> 00:03:22.080
as Emacs processes all the input.

72
00:03:22.080 --> 00:03:25.560
What I wanted was a style of programming

73
00:03:25.560 --> 00:03:28.080
where I could fire off my command

74
00:03:28.080 --> 00:03:31.560
have the Emacs command loop keep working

75
00:03:31.560 --> 00:03:33.440
and only invoke some callback

76
00:03:33.440 --> 00:03:35.280
when there was data available.

77
00:03:35.280 --> 00:03:39.560
Well Emacs is famously single threaded

78
00:03:39.560 --> 00:03:41.840
so it shouldn't come as a surprise

79
00:03:41.840 --> 00:03:44.080
that it offers a rich set of primitives

80
00:03:44.080 --> 00:03:46.720
that enable the sort of network programming

81
00:03:46.720 --> 00:03:49.320
that I wanted to do.

82
00:03:49.320 --> 00:03:50.760
In particular it offers

83
00:03:50.760 --> 00:03:53.280
a function called make network process.

84
00:03:53.280 --> 00:03:57.800
Now this method offers a bewildering variety of options.

85
00:03:57.800 --> 00:03:59.320
But at the heart of the matter

86
00:03:59.320 --> 00:04:01.040
it opens a network connection

87
00:04:01.040 --> 00:04:03.120
to some endpoint out there

88
00:04:03.120 --> 00:04:06.640
and we can configure it to be non blocking.

89
00:04:06.640 --> 00:04:09.840
It returns a handle that you can use to refer to

90
00:04:09.840 --> 00:04:14.880
this network connection with other methods.

91
00:04:14.880 --> 00:04:17.760
Other methods such as process and string

92
00:04:17.760 --> 00:04:19.600
which as the name suggests

93
00:04:19.600 --> 00:04:21.960
allows you to send textual data

94
00:04:21.960 --> 00:04:26.320
to the remote endpoint of your network connection.

95
00:04:26.320 --> 00:04:29.400
You can also use it with set process filter

96
00:04:29.400 --> 00:04:32.160
which allows you to associate a callback

97
00:04:32.160 --> 00:04:33.240
with your network connection.

98
00:04:33.240 --> 00:04:35.920
That callback will be invoked

99
00:04:35.920 --> 00:04:40.480
when there is data available

100
00:04:40.480 --> 00:04:41.960
in the processes read buffer.

101
00:04:41.960 --> 00:04:44.960
In other words in a request response oriented protocol

102
00:04:44.960 --> 00:04:47.800
like that of MPD you open your socket

103
00:04:47.800 --> 00:04:50.960
with make network process

104
00:04:50.960 --> 00:04:53.760
send your request via process send string

105
00:04:53.760 --> 00:04:56.360
and life will just continue in emacs

106
00:04:56.360 --> 00:04:57.560
until some data shows up

107
00:04:57.560 --> 00:05:00.720
in the processes read buffer

108
00:05:00.720 --> 00:05:05.200
at which point your callback will be invoked.

109
00:05:05.200 --> 00:05:07.560
It turns out this was enough

110
00:05:07.560 --> 00:05:12.280
for a purpose built async runtime.

111
00:05:12.280 --> 00:05:14.800
Let's work through the sequence of events

112
00:05:14.800 --> 00:05:16.480
when opening a connection

113
00:05:16.480 --> 00:05:18.720
and firing off a few commands in this style.

114
00:05:18.720 --> 00:05:22.120
So let's imagine a library

115
00:05:22.120 --> 00:05:25.520
that offers a connection object of some sort

116
00:05:25.520 --> 00:05:28.720
a caller and an MPD server out on the network.

117
00:05:28.720 --> 00:05:31.880
The caller will presumably get themselves

118
00:05:31.880 --> 00:05:34.760
a connection object by invoking some sort of

119
00:05:34.760 --> 00:05:38.080
connect method on our library.

120
00:05:38.080 --> 00:05:41.160
We can handle this through make network process

121
00:05:41.160 --> 00:05:45.360
but we're going to invoke make network process

122
00:05:45.360 --> 00:05:47.200
with no weight equal to true

123
00:05:47.200 --> 00:05:48.520
in other words asynchronously.

124
00:05:48.520 --> 00:05:52.240
That means the method is going to return immediately.

125
00:05:52.240 --> 00:05:56.320
We won't even know if the connection is up

126
00:05:56.320 --> 00:05:57.920
let alone what the response would be.

127
00:05:57.920 --> 00:06:01.560
This has some implications.

128
00:06:01.560 --> 00:06:05.280
At this point we've returned control to the caller

129
00:06:05.280 --> 00:06:09.400
the emacs event loop is proceeding quite happily

130
00:06:09.400 --> 00:06:11.320
and so the caller is free

131
00:06:11.320 --> 00:06:14.920
to start using our connection object.

132
00:06:14.920 --> 00:06:17.640
They might say issue a status command.

133
00:06:17.640 --> 00:06:20.600
Okay well in our library

134
00:06:20.600 --> 00:06:22.680
we don't have a connection yet.

135
00:06:22.680 --> 00:06:25.920
How on earth are we going to service this?

136
00:06:25.920 --> 00:06:29.440
Well we can simply give ourselves a queue

137
00:06:29.440 --> 00:06:33.360
and note down the fact that we owe a status command.

138
00:06:33.360 --> 00:06:35.560
That's pretty quick.

139
00:06:35.560 --> 00:06:38.120
We've now returned control back to our caller

140
00:06:38.120 --> 00:06:40.640
and they are again free to issue more commands.

141
00:06:40.640 --> 00:06:41.840
Maybe they issue a play command.

142
00:06:41.840 --> 00:06:45.160
Okay well we're going to go deeper into debt

143
00:06:45.160 --> 00:06:48.160
and note that we also owe a play command.

144
00:06:48.160 --> 00:06:56.160
At some point in the indeterminate future MPDU

145
00:06:56.160 --> 00:06:57.320
is the connection will get up

146
00:06:57.320 --> 00:07:03.000
MPDU will allocate resources to track a new client.

147
00:07:03.000 --> 00:07:06.160
They will write the welcome string into the socket

148
00:07:06.160 --> 00:07:07.920
and those bytes are going to show up

149
00:07:07.920 --> 00:07:10.360
in the emacs process read buffer

150
00:07:10.360 --> 00:07:13.160
at which point our callback will be invoked.

151
00:07:13.160 --> 00:07:17.440
We can parse the welcome string maybe

152
00:07:17.440 --> 00:07:19.240
note the version that connection object

153
00:07:19.240 --> 00:07:20.400
that might come in handy

154
00:07:20.400 --> 00:07:21.720
but the key point is

155
00:07:21.720 --> 00:07:24.080
our callback needs to take a look at the queue

156
00:07:24.080 --> 00:07:25.240
and notice

157
00:07:25.240 --> 00:07:27.200
oh we owe a status command

158
00:07:27.200 --> 00:07:29.880
and so we'll invoke process and string

159
00:07:29.880 --> 00:07:32.280
and send the status command down the pipe.

160
00:07:32.280 --> 00:07:36.760
Again at some indeterminate time in the future

161
00:07:36.760 --> 00:07:38.600
some bytes are going to show up

162
00:07:38.600 --> 00:07:41.200
in our processes read buffer

163
00:07:41.200 --> 00:07:43.160
and our callback will again be invoked.

164
00:07:43.160 --> 00:07:48.560
We've got volume is 75 plus a lot of other stuff

165
00:07:48.560 --> 00:07:50.480
and here we come to the next problem.

166
00:07:50.480 --> 00:07:54.440
If our caller invoked status

167
00:07:54.440 --> 00:07:56.960
they probably wanted to know about the status

168
00:07:56.960 --> 00:07:59.880
so how shall we get them to them?

169
00:07:59.880 --> 00:08:03.040
Well there's really not a lot of options at this point

170
00:08:03.040 --> 00:08:04.280
except the callback.

171
00:08:04.280 --> 00:08:09.000
Okay so change of plan our queue

172
00:08:09.000 --> 00:08:11.720
is no longer a queue of commands

173
00:08:11.720 --> 00:08:13.840
it's going to be a queue of commands

174
00:08:13.840 --> 00:08:15.880
with associated callbacks.

175
00:08:15.880 --> 00:08:20.280
We read the response off the socket

176
00:08:20.280 --> 00:08:23.440
invoke our caller supplied callback

177
00:08:23.440 --> 00:08:26.080
and then pop the queue.

178
00:08:26.080 --> 00:08:28.920
At this point our callback

179
00:08:28.920 --> 00:08:32.160
the library callback needs to know

180
00:08:32.160 --> 00:08:34.040
that we still have a pending command

181
00:08:34.040 --> 00:08:35.720
we fire that off down the pipe

182
00:08:35.720 --> 00:08:38.520
at some indeterminate time in the future

183
00:08:38.520 --> 00:08:40.360
we get a call we get a response

184
00:08:40.360 --> 00:08:42.640
our callback is invoked

185
00:08:42.640 --> 00:08:45.720
we invoke the caller supplied callback

186
00:08:45.720 --> 00:08:47.240
and we pop the queue.

187
00:08:47.240 --> 00:08:53.760
The structure of such a program

188
00:08:53.760 --> 00:08:55.800
is best viewed as a finite state machine

189
00:08:55.800 --> 00:08:57.640
and this is typically where you end up

190
00:08:57.640 --> 00:08:59.200
in asynchronous programming at least

191
00:08:59.200 --> 00:09:03.360
when you don't have a runtime grafted onto your program

192
00:09:03.360 --> 00:09:04.960
the way you do with Golang

193
00:09:04.960 --> 00:09:08.240
or when you don't have sort of extensive library support

194
00:09:08.240 --> 00:09:09.680
the way you do with Rust.

195
00:09:09.680 --> 00:09:14.480
Your data structure exists in one of these states

196
00:09:14.480 --> 00:09:15.440
at any given time

197
00:09:15.440 --> 00:09:18.960
and when input shows up on your file descriptor

198
00:09:18.960 --> 00:09:24.240
you transition along one of these edges to a new state.

199
00:09:24.240 --> 00:09:28.160
Cool so let's take a look at some of the code

200
00:09:28.160 --> 00:09:29.480
that flows from this.

201
00:09:29.480 --> 00:09:32.240
Okay let's hop over to an Emacs

202
00:09:32.240 --> 00:09:33.920
and take a look at how we might code this up.

203
00:09:33.920 --> 00:09:38.360
If you recall the sequence diagrams I shared

204
00:09:38.360 --> 00:09:40.120
we're going to be scribbling down the command

205
00:09:40.120 --> 00:09:42.160
and the callback that will be invoking

206
00:09:42.160 --> 00:09:43.240
upon its completion.

207
00:09:43.240 --> 00:09:45.440
So the first thing I did was give myself

208
00:09:45.440 --> 00:09:47.400
a little command struct

209
00:09:47.400 --> 00:09:52.280
with that I was able to define the connection object.

210
00:09:52.280 --> 00:09:56.280
We're going to be storing the handle to the connection.

211
00:09:56.280 --> 00:09:59.400
We're going to write down the protocol version

212
00:09:59.400 --> 00:10:02.000
that we harvest from the welcome message

213
00:10:02.000 --> 00:10:03.560
and of course we'll be recording

214
00:10:03.560 --> 00:10:05.760
the command queue as well.

215
00:10:05.760 --> 00:10:08.640
And so I gave myself a little connection object

216
00:10:08.640 --> 00:10:10.960
with a connection struct

217
00:10:10.960 --> 00:10:12.240
with those three attributes.

218
00:10:12.240 --> 00:10:15.000
With the data model squared away

219
00:10:15.000 --> 00:10:17.840
it was really pretty easy to code up

220
00:10:17.840 --> 00:10:21.160
the connect implementation.

221
00:10:21.160 --> 00:10:24.880
I'm deleting some details for exposition purposes

222
00:10:24.880 --> 00:10:29.520
but in the event it's really not that more complex

223
00:10:29.520 --> 00:10:30.520
than what you see here.

224
00:10:30.520 --> 00:10:32.840
We're going to unpack the arguments,

225
00:10:32.840 --> 00:10:35.040
figure out where the MPD server is

226
00:10:35.040 --> 00:10:37.280
to which you would like us to connect.

227
00:10:37.280 --> 00:10:39.920
We'll connect via make network process.

228
00:10:39.920 --> 00:10:42.640
We'll associate a library defined callback

229
00:10:42.640 --> 00:10:45.920
with that connection via set process filter.

230
00:10:45.920 --> 00:10:48.440
Then we'll instantiate the connection object

231
00:10:48.440 --> 00:10:50.120
and return it to the caller.

232
00:10:50.120 --> 00:10:53.800
Once the caller has a connection object

233
00:10:53.800 --> 00:10:56.880
they're free to send commands down that connection.

234
00:10:56.880 --> 00:10:59.120
So what we're doing here

235
00:10:59.120 --> 00:11:02.320
is simply instantiating a command object

236
00:11:02.320 --> 00:11:05.200
on the basis of the caller supplied arguments

237
00:11:05.200 --> 00:11:06.640
and appending it to the queue.

238
00:11:06.640 --> 00:11:07.920
And then the last thing we do

239
00:11:07.920 --> 00:11:11.040
and I've just indicated this with a comment

240
00:11:11.040 --> 00:11:12.040
is we kick the queue.

241
00:11:12.040 --> 00:11:14.560
This kind of goes back to

242
00:11:14.560 --> 00:11:18.200
the state transition diagram I laid out earlier.

243
00:11:18.200 --> 00:11:22.680
What this means is the logic for saying well

244
00:11:22.680 --> 00:11:24.280
if we're waiting the completion

245
00:11:24.280 --> 00:11:25.480
of a previously sent command

246
00:11:25.480 --> 00:11:27.280
there's really not much more to be done.

247
00:11:27.280 --> 00:11:31.000
We're just going to push this command onto the queue

248
00:11:31.000 --> 00:11:31.600
and return.

249
00:11:31.600 --> 00:11:33.120
On the other hand

250
00:11:33.120 --> 00:11:37.120
if the queue was empty on entry to LMPD send

251
00:11:37.120 --> 00:11:39.160
there's no reason not to just

252
00:11:39.160 --> 00:11:43.400
immediately send the command.

253
00:11:43.400 --> 00:11:44.680
And this is an example of

254
00:11:44.680 --> 00:11:46.520
the sort of client side code

255
00:11:46.520 --> 00:11:48.080
that results from this API.

256
00:11:48.080 --> 00:11:51.360
So you can see here we are giving ourselves

257
00:11:51.360 --> 00:11:54.240
a connection to the MPD server on the local host

258
00:11:54.240 --> 00:11:56.600
and we're going to send the get volume command

259
00:11:56.600 --> 00:11:58.160
down that connection.

260
00:11:58.160 --> 00:12:02.840
And if that command completes and all is well

261
00:12:02.840 --> 00:12:05.360
we'll just send a message to Emacs.

262
00:12:05.360 --> 00:12:07.800
Unfortunately you can't see my mini buffer

263
00:12:07.800 --> 00:12:10.960
so I'll hop over to the messages buffer

264
00:12:10.960 --> 00:12:12.720
and there's our result.

265
00:12:12.720 --> 00:12:15.160
The volume is 43.

266
00:12:15.160 --> 00:12:17.960
Great I thought.

267
00:12:17.960 --> 00:12:22.520
Simple clean responsive easy to code to.

268
00:12:22.520 --> 00:12:27.760
That is unfortunately not the end of the story.

269
00:12:27.760 --> 00:12:32.320
Let's continue this example a little bit.

270
00:12:32.320 --> 00:12:33.560
Let's imagine that

271
00:12:33.560 --> 00:12:35.920
if the volume comes back from the server

272
00:12:35.920 --> 00:12:37.360
and it is less than 50

273
00:12:37.360 --> 00:12:38.600
we would like to set it to 50.

274
00:12:38.600 --> 00:12:41.560
So this is interesting

275
00:12:41.560 --> 00:12:43.200
because we have two commands

276
00:12:43.200 --> 00:12:45.840
and whether or not we send the second command

277
00:12:45.840 --> 00:12:46.840
is going to depend on

278
00:12:46.840 --> 00:12:48.560
the response we get from the first.

279
00:12:48.560 --> 00:12:51.640
Okay I thought well that's fine

280
00:12:51.640 --> 00:12:55.080
I can simply put that logic in the callback

281
00:12:55.080 --> 00:12:57.920
that I specified for the get volume command.

282
00:12:57.920 --> 00:13:01.560
So here we are we check the return code

283
00:13:01.560 --> 00:13:04.400
we parse the volume we compare it to 50

284
00:13:04.400 --> 00:13:08.360
and if it's less we just invoke LMPD send again

285
00:13:08.360 --> 00:13:10.800
from the first command's callback.

286
00:13:10.800 --> 00:13:13.440
Okay I could live with that

287
00:13:13.440 --> 00:13:15.520
it's not the worst thing I've ever seen.

288
00:13:15.520 --> 00:13:19.400
Let's extend this example a little further

289
00:13:19.400 --> 00:13:21.480
and this is contrived but bear with me.

290
00:13:21.480 --> 00:13:25.480
Let us suppose that if we do set the volume to 50

291
00:13:25.480 --> 00:13:27.800
we'd like to get the volume one more time

292
00:13:27.800 --> 00:13:30.640
just to make sure that our change took on the server.

293
00:13:30.640 --> 00:13:33.560
Okay we can play the same game.

294
00:13:33.560 --> 00:13:37.280
We will put that logic in the callback

295
00:13:37.280 --> 00:13:39.520
that we specified for the set volume command.

296
00:13:39.520 --> 00:13:43.480
And here we are we check the return code

297
00:13:43.480 --> 00:13:45.480
we send a message to Emacs

298
00:13:45.480 --> 00:13:49.200
we send the get volume command again

299
00:13:49.200 --> 00:13:51.080
along with its own callback

300
00:13:51.080 --> 00:13:55.280
and at this point I think you know I hope it's clear

301
00:13:55.280 --> 00:13:57.520
the problem that is emerging

302
00:13:57.520 --> 00:14:01.360
and if it's not yet let's let me note that so far

303
00:14:01.360 --> 00:14:03.000
we're only handling the happy path

304
00:14:03.000 --> 00:14:04.520
in each of these callbacks.

305
00:14:04.520 --> 00:14:06.840
We really ought to do something about the error path

306
00:14:06.840 --> 00:14:10.120
for purposes of illustration let's just say

307
00:14:10.120 --> 00:14:12.120
we send a message to Emacs

308
00:14:12.120 --> 00:14:14.320
that means it would look like this

309
00:14:14.320 --> 00:14:16.560
and it's at this point

310
00:14:16.560 --> 00:14:19.400
that I really think it's impossible to deny

311
00:14:19.400 --> 00:14:23.280
that this API is actually not that easy to program to

312
00:14:23.280 --> 00:14:27.160
and if there are any JavaScript devs watching

313
00:14:27.160 --> 00:14:28.840
you're probably chuckling right now

314
00:14:28.840 --> 00:14:30.720
because I have discovered for myself

315
00:14:30.720 --> 00:14:33.880
what they call callback hell.

316
00:14:33.880 --> 00:14:36.040
If you are returning

317
00:14:36.040 --> 00:14:40.160
the results of asynchronous function invocations

318
00:14:40.160 --> 00:14:42.200
to their caller via callbacks

319
00:14:42.200 --> 00:14:45.640
you pretty much inevitably end up in this sort of

320
00:14:45.640 --> 00:14:48.040
deeply nested sequence of callbacks

321
00:14:48.040 --> 00:14:49.880
that is difficult to write difficult to read

322
00:14:49.880 --> 00:14:53.520
and difficult to reason about.

323
00:14:53.520 --> 00:14:57.480
And yet when I was stuck in this situation

324
00:14:57.480 --> 00:15:00.080
it just seemed like it really shouldn't be this bad.

325
00:15:00.080 --> 00:15:05.320
If I give myself this sort of tabular data structure

326
00:15:05.320 --> 00:15:10.160
I felt that this expressed precisely the same logic

327
00:15:10.160 --> 00:15:11.960
just in a much easier to read manner.

328
00:15:11.960 --> 00:15:15.840
I could in my mind's eye

329
00:15:15.840 --> 00:15:19.720
see the code for transforming this data structure

330
00:15:19.720 --> 00:15:21.040
which is really just a list

331
00:15:21.040 --> 00:15:25.600
into the code that you just saw in the previous slide

332
00:15:25.600 --> 00:15:29.440
and really if Lisp is good at anything

333
00:15:29.440 --> 00:15:31.080
it is list processing right

334
00:15:31.080 --> 00:15:33.080
and it was really at this point

335
00:15:33.080 --> 00:15:35.240
that a little bit of enlightenment dawned.

336
00:15:35.240 --> 00:15:40.800
I learned that Lisp is homo iconic

337
00:15:40.800 --> 00:15:46.040
which is just means that the language itself

338
00:15:46.040 --> 00:15:49.360
is a data structure in that language.

339
00:15:49.360 --> 00:15:53.160
Lisp code is after all just a list

340
00:15:53.160 --> 00:15:57.160
and the power of Lisp macros

341
00:15:57.160 --> 00:15:59.760
is taking that data structure

342
00:15:59.760 --> 00:16:02.400
some data structure that you've defined

343
00:16:02.400 --> 00:16:04.640
and doing exactly what I wanted to do

344
00:16:04.640 --> 00:16:07.520
transforming it from one list into another

345
00:16:07.520 --> 00:16:11.080
the destination list being Lisp code.

346
00:16:11.080 --> 00:16:16.000
So I got busy and I coded up my first Lisp macro

347
00:16:16.000 --> 00:16:19.160
which I called LMPD chain

348
00:16:19.160 --> 00:16:21.600
and that lengthy list of you know

349
00:16:21.600 --> 00:16:24.200
three or four nested callbacks

350
00:16:24.200 --> 00:16:25.920
gets turned into this

351
00:16:25.920 --> 00:16:29.520
which I hope you'll agree is much simpler

352
00:16:29.520 --> 00:16:32.240
much easier to read much easier to reason about.

353
00:16:32.240 --> 00:16:36.000
And if you're morbidly curious

354
00:16:36.000 --> 00:16:40.160
you can you can expand your macros

355
00:16:40.160 --> 00:16:44.200
and this invocation of LMPD chain expands to this.

356
00:16:44.200 --> 00:16:46.400
So that's my story.

357
00:16:46.400 --> 00:16:50.840
In all fairness I should note that

358
00:16:50.840 --> 00:16:55.160
the MPD protocol has some subtleties and complexities

359
00:16:55.160 --> 00:16:56.880
that I didn't really get into

360
00:16:56.880 --> 00:16:58.360
both due to time constraints

361
00:16:58.360 --> 00:17:00.520
and because they're not terribly relevant

362
00:17:00.520 --> 00:17:02.000
to the points I wanted to touch on

363
00:17:02.000 --> 00:17:05.360
I should also note that there's

364
00:17:05.360 --> 00:17:07.720
a fair amount of work in the library itself

365
00:17:07.720 --> 00:17:11.240
around accumulating partial responses

366
00:17:11.240 --> 00:17:12.560
as they show up in the buffer

367
00:17:12.560 --> 00:17:16.120
and dispatching them piecemeal to the caller

368
00:17:16.120 --> 00:17:19.720
that was really too complex to get into here.

369
00:17:19.720 --> 00:17:22.360
If you would like to see the code

370
00:17:22.360 --> 00:17:25.080
it's available on GitHub as well as Melpa.

371
00:17:25.080 --> 00:17:29.200
I'll be putting a version of this talk

372
00:17:29.200 --> 00:17:30.480
on my personal site

373
00:17:30.480 --> 00:17:33.720
and you can always reach out to me personally

374
00:17:33.720 --> 00:17:36.960
I hang out on IRC as SPIF

375
00:17:36.960 --> 00:17:41.920
or you can just email me as SPIF at P.O.Box dot com.

376
00:17:41.920 --> 00:17:47.880
Thank you very much.