In some contexts, I would find convenient if block was an expression, whose value is the value of the last expression in the block. That is, I would like to be able to do
let y = block:
let x = 5
echo "hi"
x * x
assert y == 25
This is particularly apparent with Rosencrantz, a DSL I have made to write web servers. A typical application may look like this:
let handler = get[
path("/login")[
ok("Welcome")
] ~
path("/logout")[
ok("Goodbye")
]
]
Here each element (get, path("/login"), ok("Welcome")) is a handler, that is, a function of type (req: ref Request, ctx: Context) => Future[Context], and such handlers are composable with the [] and ~ operators.
Apart from trivial cases, handlers will need to perform custom logic. I would like to be able to write
let handler = get[
path("/login")[
block:
let user = ...
let loggedIn = somethingElse()
if loggedIn: ok("Welcome") else: complete(Http401, "Invalid login")
] ~
path("/logout")[
ok("Goodbye")
]
]
but I cannot, because block is a statement.
I have tried writing a template to make a custom block equivalent that is instead an expression, like this:
template scope(body: stmt): expr =
proc inner(): auto =
body
inner()
but when I try
let y = scope:
let x = 5
echo "hi"
x * x
I get Error: wrong number of arguments.
Is there a way to define something that works like block but is an expression instead? Or maybe it would be best to change block and make that an expression altogether?
find convenient if block was an expression,
Related to that is an remark of Jehan:
I note that this could be done by allowing for loops (and possible while loops) as expressions. (2016-01-30 18:17:45)
Using Jehan's enumerate as inspiration i got this:
template scope(body: untyped): untyped =
proc inner: auto = body
inner()
let y = scope do:
let x = 5
echo "hi"
return x * x
assert y == 25
Mainly add do and returnGreat, this seems to work! I had to change it to
template scope(body: untyped): untyped =
proc inner: auto {.gensym.} = body
inner()
to make sure that different calls to scope do not conflict, but it seems to work well otherwise :-)
this works by the way:
let y = (block:
let x = 5
echo "hi"
x * x
)
assert y == 25
Oh, scope template is so useful. It seems ML-language and Ruby.
import sequtils
template mapx*[T](xs: seq[T], fbody: expr): auto =
proc f(x: T): auto {.gensym.} =
let x {.inject.} = x
fbody
map(xs, f)
let xs = toSeq(0..2).mapx(x + 1)
xs
.mapx do:
let y = x * x
echo y
return y
.mapx do:
result = newSeq[float]()
for i in 0..2:
result.add float (x + i)
.echo
let xss = xs.mapx do:
let x1 = x
return xs.mapx do:
return x1 * x
Using Krux's snippet as a starting point i got this macro which removes the creation of possible closures unlike my last snippet.
import macros
macro scope(body: untyped): untyped =
if kind(body) != nnkDo: body
else: newBlockStmt(body[6])
let y = scope do:
let x = 5
echo "hi"
x * x
assert y == 25
Thank you Arrrrrrrrr. Thanks to your macro, and Jehan's enumerate, Petermore's nimLazy, Def's iterutils,.... I made it I wanted to do. I touched Nim only a few days, but I was able to experience Nim's great expression.
import macros
macro scope(body: untyped): untyped =
if kind(body) != nnkDo: body
else: newBlockStmt(body[6])
proc toIter*[T](i: iterator: T): iterator: T =
i
template map*[T](i1: iterator: T, arg1, fbody :untyped): iterator(): auto =
proc f(x: T): auto {.gensym.} =
let `arg1` {.inject.} = x
scope fbody
type S = type((
block:
var x: T
f(x)
))
let i = toIter(i1)
iterator i2: S {.closure, gensym.} =
for x in i():
yield f(x)
i2
iterator count3: int {.closure.} =
for x in 1..3:
yield x
proc plot*[T](i: iterator: T {.closure.}) =
for x in i():
echo x
count3
.map(a, a + 1)
.map(b) do:
let c = float b + 10
echo "hi"
c
.plot
Additionally, this is the lengths comparison I used.
map(x,)
map(x)do:
map((x)=>)
map((x)=>(block:))
map((x)=>scope do:)
map do(x:auto)->auto:
map(proc(x:auto):auto=)
There are situations in which the scope macro and (block: ) solution fails, see example
This new macro solves the issue:
macro scope*(body: untyped): untyped =
if kind(body) != nnkDo: return body
else:
result = newNimNode(nnkStmtListExpr)
for child in body[6]: add(result, child)
I suppose the failed codegen thing should be considered a bug.