Ruby has a very nice feature – a special syntax to pass a block of code as an argument to a method. I did want something like this NGS. Background, reasons and what this feature became follow.
Background
NGS – Next Generation Shell is a language and a shell I’m working on for a few years now.
This article is about new NGS syntax inspired by and compared to the counterpart Ruby’s do ... end
syntax. The new syntax provides more convenient way of passing anonymous function to another function. Passing functions to other functions is (sometimes) considered to be a “functional programming” feature of a language.
In NGS, up until recently you had to pass all of your arguments (except for the first argument which can be passed before .method()
) inside the parenthesis of the method call syntax:
mymethod(arg1, arg2, ... argN)
or syntactically equivalent
arg1.mymethod(arg2, arg3, ... argN)
Passing an anonymous function could look like the following in NGS:
required_props.each(F(prop_name) {
prop_name not in rd.anchor throws InvalidArgument("...")
})
The problem
In the code snippet above I don’t like the closing })
part. A single }
would look much better. You could use the each operator %
to get rid of the closing )
. So the code above becomes
required_props % F(prop_name) {
prop_name not in rd.anchor throws InvalidArgument("...")
}
Which is shorter but not very clear (at least for newcomers) because of the %
operator. I prefer to use the each operator in small expressions such as myarr % echo
and not to use it in bigger expressions such as the snippet above. The closing )
syntax issue is not solved for the general case then so I wanted something like Ruby’s do block syntax.
Importing the “do” block
I’ve made quite a few modifications to fit NGS and to fit some OCD-ness:
The keyword
I prefer “with” because in my opinion, it’s much clearer that it links left-hand-side and right-hand-side. mymethod() with { ... some code ... }
looks more clear to me than mymethod() do { ... some code ... }
. It might be that I was affected by constructing a mini test framework where test("my feature") with { test code }
looks very good.
Not only for code blocks
I don’t like “special” things so whatever comes after with
can be any expression.
required_props.each() with F(prop_name) {
prop_name not in rd.anchor throws InvalidArgument("...")
}
or
results = mylist.map() with my_super_mapper_func_name
Note than when using each
it could probably be clearer to have the do
keyword. I’m still unsure that with
is the right choice
Code blocks syntax
You might ask how test("my feature") with { test code }
works if what’s after with
is any expression.
The test("my feature") with { test code }
syntax works because { ... }
is a syntax for anonymous function (with 3 optional parameters called A
, B
and C
) and not because it’s part of the with
syntax. You could get the same result using test("my feature", { test code })
.
Not just one
I don’t see any reason why with would be limited to one occurrence.
finally()
with {
while entry = c_readdir(d) {
...
}
}
with {
r = c_closedir(d)
r != 0 throws DirFail('...')
}
Not a special parameter
As compared to Ruby, the finally()
method does not use the special yield
keyword to run the given arguments nor parameter should be declared with &
. Here is the definition of finally()
method (including the bug that cleanup can run twice, which I intend to fix).
F finally(body:Fun, cleanup:Fun) {
try {
ret = body()
cleanup()
ret
} catch(e) {
cleanup()
throw e
}
}
Not special at all, just an additional syntax for arguments
The with
syntax allows to write method call arguments outside the parenthesis. That’s all. What follows from this definition is the possibility of using keyword arguments:
finally()
with body = {
while entry = c_readdir(d) {
...
}
}
with cleanup = {
r = c_closedir(d)
r != 0 throws DirFail('...')
}
Let’s shorten the syntax
The with name = val
argument syntax looked a bit too verbose for me and with
was stealing attention focus from the name. I’ve added the fat arrow syntax to combat that.
finally()
body => {
while entry = c_readdir(d) {
...
}
}
cleanup => {
r = c_closedir(d)
r != 0 throws DirFail('...')
}
How the feature looks like now?
The snippet above refers to real code in stdlib. Additional examples of the with
syntax follow:
Stdlib’s retry()
F body_missing_in_retry() throw InvalidArgument("...")
F retry(times=60, sleep=1, ..., progress_cb={null}, success_cb={A},
fail_cb=null, body:Fun=body_missing_in_retry) {
...
}
F assert_resolvable(h:Str, title="Resolve host", times=45, sleep=2) {
retry(times=times, sleep=sleep, title=title)
body => { `dig "+short" $h`.lines() }
success_cb => { log_test_ok("...") }
fail_cb => { throw TestFail("...") }
}
test()
mini framework for … tests!
F test(name:Str, f:Fun) {
...
}
Usage:
test("Resolving gateway FQDN $gw_fqdn") with {
assert_resolvable(gw_fqdn, "Resolve gateway FQDN")
}
Have your own ideas?
Please comment with your ideas regarding this, other and proposed NGS features, they can make it into the language. Or fork/improve/pull request.
Have a nice weekend!