Open dom96 opened 3 years ago
do
?:
template fn(n: int, a, b) =
for i in 0..<n:
a
b
fn(3) do: echo 1 do: echo 2
* ditto with `quote do`
@timotheecour please read the RFC, it explicitly talks about do
notation for anonymous procs, not about do notation in quote do or for templates/macros
Added a clarification at the bottom of my post since the title is the only one that mentions this.
@timotheecour please read the RFC, it explicitly talks about do notation for anonymous procs, not about do notation in quote do or for templates/macros
still, how do you express this without do
?
proc fn[T1, T2](a: T1, b: T2) =
a(1)
b(2)
fn do(x: int):
echo "before" # example block, can span multiple lines
echo x
do(y: int):
echo "before"
echo y
I'm not sure how to use proc(x: int)
in this context, the only thing that I found was adding a dependency on sugar
and using:
import sugar
fn((x: int) => (block:
echo "before"
echo x),
(y: int) => (block:
echo "before"
echo y))
not 100% clear which is better here
Easy, like so:
proc fn[T1, T2](a: T1, b: T2) =
a(1)
b(2)
fn( (proc(x: int) =
echo "before" # example block, can span multiple lines
echo x),
(proc (y: int) =
echo "before"
echo y)
)
good enough, thanks!
The do() anonymous notation provides key ergonomics to our RPC protocols: https://github.com/status-im/nimbus-eth2/blob/dba39c5/beacon_chain/beacon_node.nim#L692-L768.
Using proc with parenthesis would be too lispy for my tastes. While I felt do was always forein/magic/alien, anonymous proc are really annoying otherwise.
On that topic the recurrent need of {.nimcall.}
or {.closure.}
explicit annotations is another ergonomic meh.
I would like to point out that this success story and appealing use of RPC on embedded is thanks to the do notation: https://forum.nim-lang.org/t/6916
import nesper/servers/rpc/rpcsocket_json
# import nesper/servers/rpc/rpcsocket_mpack
# Setup RPC Server #
proc run_rpc_server*() =
var rt = createRpcRouter(MaxRpcReceiveBuffer)
rpc(rt, "hello") do(input: string) -> string:
result = "Hello " & input
rpc(rt, "add") do(a: int, b: int) -> int:
result = a + b
rpc(rt, "addAll") do(vals: seq[int]) -> int:
result = 0
for x in vals:
result += x
echo "starting rpc server on port 5555"
logi(TAG,"starting rpc server buffer size: %s", $(rt.buffer))
startRpcSocketServer(Port(5555), router=rt)
Hence, I would welcome the change but only if an alternative syntax less clunky than proc with parenthesis is available.
I appreciate the fact that Nim allows me write generic code that works fine both when something is implemented as a template or a proc. Adding pointless restrictions to the do
notation will break this property and it's not going to make the language any simpler. After all, the proposed implementation will just add more rules that the compiler must enforce and user must cater to.
@mratsim I think your example is poorly chosen as this works just as well (I think):
rpc rt, "hello", proc input: string): string =
result = "Hello " & input
Well, "easy" or not, it's clearly not as ergonomic and thus, I'd argue, less idiomatic.
If you must remove it, I think a substitute that preserves legibility needs to be introduced.
I would like to point out that this success story and appealing use of RPC on embedded is thanks to the do notation: https://forum.nim-lang.org/t/6916
This is actually what inspired me to create this RFC. My take on this is that it would read far better with proc
, I haven't tested this but I think this would work:
import nesper/servers/rpc/rpcsocket_json
# import nesper/servers/rpc/rpcsocket_mpack
# Setup RPC Server #
proc run_rpc_server*() =
var rt = createRpcRouter(MaxRpcReceiveBuffer)
rpc(rt, "hello",
proc (input: string): string =
result = "Hello " & input
)
rpc(rt, "add",
proc (a: int, b: int): int =
result = a + b
)
rpc(rt, "addAll",
proc (vals: seq[int]): int =
result = 0
for x in vals:
result += x
)
echo "starting rpc server on port 5555"
logi(TAG,"starting rpc server buffer size: %s", $(rt.buffer))
startRpcSocketServer(Port(5555), router=rt)
As for the examples where multiple procs are passed in... once you're passing in more than one proc into a function call then you should be starting to consider refactoring that out. I would argue that we shouldn't be encouraging it via syntax sugar implemented in the compiler.
this is the best notation I can think of, and it's better than do
objectively (less foreign, easier to grok etc):
block:
proc rpc[T](a: int, b: T) = echo (a, $b.type)
rpc 1,
proc(c:int) = echo 1.2 # single line
rpc 2,
func(c:int): int =
# can use `func`, unlike `do` (and other proc kinds pending alias PR #11992)
# multiline
c*c
As for the examples where multiple procs are passed in... once you're passing in more than one proc into a function call then you should be starting to consider refactoring that out. I would argue that we shouldn't be encouraging it via syntax sugar implemented in the compiler.
it also works out nicely, see below:
# with multiple procs:
proc rpc2(a, b: int, b1: auto, b2: auto) = echo (a, $b1.type, $b2.type)
rpc2 1, 2,
func(c: int): float = 1.2,
proc(x, y: int) = echo (x,y)
rpc2 1, 2,
func(c: int): float =
discard,
proc(x, y: int) =
echo (x,y)
rpc2 1, 2,
func(c: int): float = 1.2,
proc(x, y: int) =
echo (x,y)
there is just 1 gotcha but it's acceptable and it's still better than using do
which does look foreign:
fn 123,
proc(x: int) =
#[
this doesn' work:
echo x,
this works:
echo(x),
(echo x),
discard,
]#
echo(x),
proc (y: int) =
echo y
block:
proc fn[T1, T2](a: T1, b: T2) = discard
# use this (preferable IMO)
fn proc(x: int): auto =
echo(1),
proc (y: int) =
echo y
# or this:
fn(
proc(x: int) =
echo x
, proc (y: int) =
echo y)
# but not this:
# fn proc(x: int): auto =
# echo(1)
# , proc (y: int) =
# echo y
I'd say that the user should be incentivized to remove code that is hard to read, but I can't think of a technical reason to deny the code, so not supporting better syntax just seems like petty laziness.
An alternative to do
notation for anon procs could be implemented with macros:
proc example(callback: proc (i: int)) = callback(1)
# this is parsable:
example <- (i: int):
echo i
# i personally prefer words over operators
pass example, (i: int):
echo i
One thing I'm not sure about is the syntax for return types. Obviously :
cannot be used, ->
also is problematic because of precedence, but that could be solved by making it into another macro which adds a return type to the closure added by <-
/pass
. Another option would be Go-style example <- (i: int) int
, but I think it's a bit ugly.
how about changing do:
to something more intuitive like lambda
?
how about changing
do:
to something more intuitive likelambda
?
The issue raised here is that do
is a special syntax. I doubt renaming it would help, and we can't just randomly take the name lambda
and turn it into a keyword.
Many points have been made already but downvoting without saying something feels wrong, do notation creates the most expressive way of using a macro in my library https://github.com/hlaaftana/applicates, it is the best way to mirror routine declaration syntax in macros IMO. Yeah we have to do ->
instead of :
but it's not like that changes anything. Type sections are ugly enough to pass to macros at least keep the syntax that supports routine argument syntax for macros.
@hlaaftana Looking at the example in your readme, this:
doAssert @[1, 2, 3, 4, 5].map(applicate do (x): x - 1) == @[0, 1, 2, 3, 4]
could easily be replaced with sugar.=>
:
doAssert @[1, 2, 3, 4, 5].map(applicate((x) => x - 1)) == @[0, 1, 2, 3, 4]
and for multiline procs, Araq's idea works well:
doAssert @[1, 2, 3, 4, 5].map(applicate proc (x) =
let a = x + 2
a - 3
) == @[0, 1, 2, 3, 4]
=>
is worse. You can't use semicolons between arguments and return types are going to be extremely ugly, if you were going to support them you would need parentheses or something, and to handle the parentheses you have to distinguish them from (a)
vs a
, and (a, b)
counts as parentheses but (a, b: int)
and (a: int, b: int)
are both named tuple constructors somehow. Not just that, I'm not trying to work with anonymous procs here (I believe you are trying to tell me to use the output of sugar.=>
?), I'm trying to generate anonymous templates, and the most expressive argument syntax for them is with do
.
The
do
notation has rightly been kept "experimental" (https://nim-lang.org/docs/manual_experimental.html#do-notation) for the 1.0 releases. Every time I see it used it feels innately un-Nim to me, when I mentioned this to other Nim users they have a very similar reaction.Objectively speaking these are the problems with it:
->
.=>
operator).Let's simplify the language and deprecate this feature to discourage its use, then eventually remove it.
Note: I am proposing this for the do notation which applies to anon procs specifically. Not the variant which allows passing multiple code blocks to a template.