-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[TODO] Nim now supports true lambdas; eg allows map(a~>a*localVar) (wo limitations of sugar.nim =>
)
#8679
Conversation
=>
)
=>
)=>
)
5496cb6
to
29db6fb
Compare
I like |
Glad you find a better solution @timotheecour! I'm not yet familiar enough with the macro infrastructure to fully understand the changes, so I have a few doubts:
|
Yes, a An enhancement could be made to let a proc accept a lambda in cases where type inference is doable at call site (as done in sugar's =>) but that's not essential IMO.
|
Nice! For variadic map, you can have a look into how I do it in loop-fusion: https://github.com/numforge/loop-fusion. I can update sequtils later. Ideally those should work on all "Indexable" containers from nim-lang/RFCs#50. Unfortunately it will only work for seq/array/openarray at the moment, for truly generic maps I'm pending #7737.
There is no convention, you have to know sequtils to understand You can argue that it's a small thing to learn but lots of small things adds up. |
There is nothing wrong with |
Maybe we should add the convention to NEP-1? (it's still sad that mapIt doesn't nest) |
"Nim -- how do you write |
I always saw it as one of Nim's weaknesses because
The latter point was always the biggest issue for me. It means that you have to switch styles when going from unnested to nested structures, which always looks inconsistent. And when working with 2-d structures like e.g. tables nested iteration isn't rare because you often operate on elements of rows/columns. @timotheecour Does using untyped arguments for |
@mratsim (#8675 (comment)), @drslump (#8675 (comment)):
The "it" is not from "nowhere". The "it" notation comes from Common Lisp's Anaphoric macros. For more, read starting of Ch.14. Anaphoric Macros from On Lisp book [free pdf, html version].
The anaphoric (-map (lambda (n) (* n n)) '(1 2 3 4)) ;; normal version
(--map (* it it) '(1 2 3 4)) ;; anaphoric version |
On the contrary, universally applied names like |
This is a really cool PR but I think that overloading The Anaphoric convention (as referenced by @kaushalmodi, TIL btw :)) is great. But I can't help but wonder, can we naturally extend it to more than one variable? For example, how could we define a |
=>
)=>
)
0b95696
to
b74addd
Compare
see
it shouldn't conflict (please provide example if not); furthermore this PR's /cc @bluenote10
fixed; I now used |
If this is accepted I think Just a thought: what if the let s = [1,2,3]
echo s.map2(a => a + 1)
echo s.map2(it + 1) This option might be less controversial, and should be possible to implement. |
note that I intend to make let s = [1,2,3]
echo s.map2(a => a + 1)
echo "AbC".map2(toLower) that's not magical since the proc would be expected to be in scope for fallback to # What if `it`, `a`, or `b` is in scope?
let it = 10 # could be from a nested lambda for example, or from a local var, eg in an import...
echo s.myfun(it) # is that `a=>a` or `a=>it` ?
# what if `it`, `a, or `b` is not mentioned in RHS (ie, a projection)
echo s.myfun(a => c) # ok, it's projection: ignores input and returns local var `c`
echo s.myfun(c) # is that `a => 10`? `(a,b) => c` ? `() => 10` ? impossible to tell from looking at this
echo s.myfun(a) # is that a=>a? (a,b) => a? (a,b,c)=>a ? or maybe it=>a where `a` is a local?
# even trickier:
echo s.myfun(a => (b => 10 + b)) # without any magic `makeLambda` overload, this is not ambiguous
# but with `it` shorthand this is:
echo s.myfun(b => 10 + b) # but that already has a different meaning...
# and it gets even trickier with UFCS
so I'd rather not conflate |
My proposal would only concern
If The other issues can easily be solved by requiring that an
Changing conventions is always a disadvantage. |
The We should flip this the other way around. This PR should use something other than |
I think we should fix up EDIT: I think the only real issue with @timotheecour |
Due to operator precedence rules I suppose it must start with The 2 big languages with lots of esoteric symbols are C ( See C operator reference and Haskell Hoogle Thoughts? |
98b4b1f
to
700a7a3
Compare
=>
)=>
)
/cc @dom96 PTAL
actually, no, it must be an arrow operator, defined here: https://github.com/nim-lang/Nim/blob/devel/doc/manual.rst#L573
looks like we need to keep let's delay new features to future PR's (eg as I listed in top-level PR msg) |
That's much better, but sadly there are still a couple of things that I dislike with this approach. If we can fix these then there is a chance I would accept this (please note that this isn't a guarantee that it will be accepted, you need to convince @Araq and the implementation needs to be sound). My main problem is that defining the template map[T](s: openarray[T], ctl: static[CompileTimeLambda[T, T]]): seq[T] =
result = @[]
for i in s:
result.add ctl(i) Where This sounds doable as long as:
But the benefits are huge and would make writing operations this way really easy. |
/cc @dom96 @Clyybber @skellock when defined(case1):
type CompileTimeLambda[T]=object
template foo[T](expr: CompileTimeLambda[T]): untyped = discard
when defined(case2):
type CompileTimeLambda[T]=object
template foo[T](expr: static[CompileTimeLambda[T]]): untyped = discard
when defined(case3):
type CompileTimeLambda[T]=concept T
template foo[T](expr: static[CompileTimeLambda]): untyped = discard
when defined(case4):
type CompileTimeLambda=concept b
template foo(expr: static[CompileTimeLambda]): untyped = discard
when defined(case5):
type CompileTimeLambda=concept b
template foo(expr: CompileTimeLambda): untyped = discard
# Error: undeclared identifier: '~>'
foo(a~>a) if you have a concrete counter-proposal to this PR, please show me some sample code (I'd be very happy to be proven wrong!) Note: in D, they solve your concern (allowing specifying type constrains) via template constraints; this is not currently possible in Nim but see my RFC for that: [1]
|
Sorry, but no. The last thing Nim needs is yet-another-way to write lambdas. |
=>
)=>
)
I found a clean design to support lambdas in Nim via
a ~> some_expr(a)
EDIT it now works with 0 or more arguments, eg:
() ~> expr
;a~>expr
,(a,b)~>expr
advantages over
=>
from sugar.nim=>
because the code is inlined (instead of requiring a function pointer indirection)=>
from `sugar.nim (see type inference with lambdas doesn't work (requires explicit types) #7435 : sugar's => requires type annotations in generics)=>
(see future/sugar=>
syntax breaks with generics #7816)advantages over something like
mapIt(expr(it))
orfoldl(expr(a,b))
it
is too magical, unhygienic and less familiar than~>
(eg [1] [2])a,b
infoldl
is even worse, as it's not even infold
function name (the user has to know which template uses the magic variables)mapIt
doesn't allow nesting (it
, eg:file.byLine.mapIt(it.split.mapIt(bar(it)))
won't workmapIt
style could be extended for passing multiple lambdaslambda(a)
) use inside the template that uses the lambda: no need forinject
(1~>it, 2~>a,b etc)
inmapIt
,foldl
)example usage
fixes these:
(provides a better way for lambdas than sugar's
=>
which are not as powerful)map
shouldn't care whether proc arg iscdecl
map
shouldn't care whether proc arg iscdecl
#8303=>
syntax breaks with generics future/sugar=>
syntax breaks with generics #7816=>
in generic Cannot instantiate T from=>
in generic #7399(provides a better way)
tasks for future PR's
makeLambda
makeLambda
(a:int, b) => expr(a,b)
map2
; maybe just name itmap
and make sure everything works as before for clients of previousmap
functionmapIt
;map2
is cleanerdo
notation given this? (eg:f2 = filter(colors) do (x: string) -> bool : x.len > 5
)=>
from sugar.nim given this?return a function pointer
Notes
[1] #8675 (comment))
[2] #8675 (comment)