Friday, March 07, 2008

New-style slots in io.launcher and smtp

Yesterday I refactored the io.launcher and smtp libraries to use Eduardo's new-slots and the results were quite nice.

Prior to this, words such as run-process would take either a string, an array of strings, or an assoc. The latter was used to specify more advanced launch parameters, such as I/O redirection. Here is a typical example of the old way:
{
{ +command+ "make clean install" }
{ +stdout+ "../build-log" }
{ +stderr+ +stdout+ }
} run-process

This looks nice and neat, however, if some of the values are computed, then you cannot use a literal assoc, you have to construct one. Suppose we want to add a 5 minute timeout. Here is one way:
[
"make clean install" +command+ set
"../build-log" +stdout+ set
+stdout+ +stderr+ set
5 minutes +timeout+ set
] { } make run-process

Having to change all of the code just because one parameter becomes computed is not so nice. The other alternative is to use something like bake:
{
{ +command+ "make clean install" }
{ +stdout+ "../build-log" }
{ +stderr+ +stdout+ }
{ +timeout+ %[ 5 minutes ] }
} bake run-process

This is better but now you have bake, %[ which don't pertain to your problem domain, plus some unnecessary Lisp-style nesting. And what if you wanted to build up a descriptor in one word, but then have another word which amended it further? You'd have to bake two assocs and use an assoc word such as 'union'. Bake is nice in other contexts but it is the wrong solution here.

Now suppose we used a tuple of process launch parameters:
<process>
"make clean install" over set-process-command
"../build-log" over set-process-stdout
+stdout+ over set-process-stderr
5 minutes over set-process-timeout
run-process

This is more composable; you can write a word which takes a descriptor, and sets some slots in it. It is also relatively easy to have both literal launch parameters and computed ones, and adding a computed parameter to an all-literal descriptor doesn't require introducing make-assoc or bake. However, now we've introduced some stack shuffles and the accessor names are kind of long.

Enter new-slots:
<process>
"make clean install" >>command
"../build-log" >>stdout
+stdout+ >>stderr
5 minutes >>timeout
run-process

This is about as ideal as it gets. No unnecessary nesting, no stack shuffling, and the only words that appear directly pertain to your problem domain.

What about the case where a launch parameter comes off the stack? Suppose the command name is an input and everything else is computed on the spot. Here is the old way:
[
+command+ set
"../build-log" +stdout+ set
+stdout+ +stderr+ set
5 minutes +timeout+ set
] { } make run-process

The new way introduces a swap, but there is still a net reduction in complexity:
<process>
swap >>command
"../build-log" >>stdout
+stdout+ >>stderr
5 minutes >>timeout

Now suppose we have a make target name on the stack and we want to run make with that target. Here is the old way:
[
"make" swap 2array +command+ set
"../build-log" +stdout+ set
+stdout+ +stderr+ set
5 minutes +timeout+ set
] { } make run-process

With new-slots:
<process>
"make" rot 2array >>command
"../build-log" >>stdout
+stdout+ >>stderr
5 minutes >>timeout

With bake and new-slots:
<process>
swap { "make" , } bake >>command
"../build-log" >>stdout
+stdout+ >>stderr
5 minutes >>timeout

I find the latter preferrable to the first two.

The situation with smtp is a little different. Previously, I had a send-simple-message word which took a body, subject, from and to on the stack:
"Blahblah" "Hi" { "alice@aol.com" "joe@aol.com" } "bob@aol.com" send-simple-message

Clearly, this doesn't scale as additional parameters are added; header lines, attachments, etc. The new code uses new-slots and it is a lot more aesthetically pleasing:
<email>
"Blahblah" >>body
"Hi" >>subject
{ "alice@aol.com" "joe@aol.com" } >>to
"bob@aol.com" >>from
send

Not only does it read better but also it is easy to add additional fields such as CC, BCC, attachments, S/MIME support, etc. (Well, easy enough to add the fields; actually implementing some of these is another matter altogether...)

New-slots is going into the core very soon now. Bake will need to be worked on some more; then it will go in the core and replace usages of make there. Make will go into a library in extra and be phased out along with old slots.

3 comments:

Adam said...

What other libraries are planed for a move into core?

Anonymous said...

nice. one question .. is the <email> tuple predefined somewhere (in the same vocab as "send" probably) or it is defined (what slots it contains) and created right there?

Slava Pestov said...

middayc: the email tuple is defined in the smtp vocab.