-
-
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
every symbol becomes 1st class; defines 0-cost lambda and aliases; generics/iterators/templates/etc can be passed to any routine #11992
Conversation
a2b2258
to
7082319
Compare
just rebased (it was previously green but had started to bitrot) /cc @Araq |
effe211
to
21c9ad3
Compare
rebased and workaround after new gensym handling |
21c9ad3
to
64f591d
Compare
rebased again against latest devel /cc @Araq ; this PR (which garnered a lot of +1's) would fill an important gap in nim IMO; |
Looks fantastic!! Please merge this ASAP, perhaps release it for |
64f591d
to
c41e90a
Compare
This really needs a RFC process. Negative sides:
|
here: when true:
import std/lambdas
template typedesc2[T](a): bool = a is type and a is T
# this currently works:
proc bar1(T: aliassym): auto = T.default
doAssert bar1(alias2 float) == 0.0
proc bar2(T: aliassym): auto {.enableif: typedesc2[SomeInteger](T).} = T.default
doAssert bar2(alias2 uint8) == 0'u8
when false:
# this hasn't yet been implemented but will work:
proc bar3(T: aliassym): seq[T] = discard
# pending future enableif syntax sugar:
proc bar2(T: typedesc2[SomeInteger]): seq[T] = discard
just look at how much easier it is to write a correct+efficient Also note that sequtils.toSeq is still incorrect for openArray (multiple evaluation bug, and it's really hard if not impossible to fix once you understand the problem), whereas with this PR, it just works: import std/lambdas
template isIterable(a): bool = typeof(block: (for ai in a: ai)) is type
proc toSeqImpl(a: auto): seq {.enableif: isIterable(a).} =
type T = typeof(block: (for ai in a: ai))
when compiles(a.len):
result = newSeq[T](a.len)
var i=0
for ai in a: (result[i] = ai; i.inc)
else:
result = newSeq[T]()
for ai in a: result.add ai
template toSeq(a: untyped): untyped = toSeqImpl(lambdaIter a) # sugar to avoid calling code having to write `lambdaIter`
doAssert toSeq(10..13) == @[10,11,12,13]
iterator iota(n: int): int = (for i in 0..<n: yield i)
doAssert toSeq(iota(3)) == @[0,1,2]
doAssert @[10,11].toSeq == @[10,11]
doAssert [10,11].toSeq == @[10,11]
doAssert not compiles toSeq 10
# works with openArray, safe wrt multiple evaluation bug, unlike sequtils.toSeq!
from sequtils import nil
proc fn3(a: openArray[int]) =
template fn(x: int): untyped = (echo "in fn"; x)
echo "this PR toSeq: no bug"
doAssert toSeq(toOpenArray(a,0,fn(1))) == @[10,11]
echo "sequtils.toSeq: multiple evaluation bug and hard/impossible to fix"
doAssert sequtils.toSeq(toOpenArray(a,0,fn(1))) == @[10,11]
fn3 @[10,11] prints:
|
As long as your return type remains |
|
Notwithstanding the technical discussion in the PR, having first class symbols and composable iterators would be a huge boost to expressiveness of the language. |
This pull request has been automatically marked as stale because it has not had recent activity. If you think it is still a valid PR, please rebase it on the latest devel; otherwise it will be closed. Thank you for your contributions. |
This pull request is stale because it has been open for 1 year with no activity. Contribute more commits on the pull request and rebase it on the latest devel, or it will be closed in 30 days. Thank you for your contributions. |
This pull request has been marked as stale and closed due to inactivity after 395 days. |
rip |
Although this PR is incredible. It has been a struggle to maintain the existing features of the Nim language. What really is needed for now is to refactor existing features and make it accessible among backends. Anyway the stale label means eveyone could pick it up and complete it. |
This PR makes every symbol 1st class, which enables these:
const myecho = alias echo
) that work with any symbol; can also return a tuple, eg(fun1: alias echo, fun2: alias doAssert)
x~>x mod 2 == 0
,(x,y) ~> x*y
,lambdaIt: it*2
etc), which are defined in library code on top ofalias
toSeq
and lazymap/filter/join
etc; which can have speed benefit compared tosequtils
); the resulting code is quite nice and compact, seetlambda_iterators.nim
which uses librarylambdaIter
that builds on top oflambda
; it allows defining primitives that work regardless we're passing a value (@[1,2,3]
) or a lazy iterate (egiota(3)
)Extensive unittests are included, see
tlambda.nim
,tlambda_iterators.nim
fixed issues
see tests: every symbol can be passed to every routine, eg
foo(alias mysym)
see tests
see tests, eg:
myfun(10, a~>a*2)
+
called - lib/system.nim(1102, 12): Cannot instantiate T #5702 (see unittests; can pass builtin+
just like any other symbol)=>
) #8679 (lambdas); current PR has none of the drawbacks of [TODO] Nim now supports true lambdas; eg allows map(a~>a*localVar) (wo limitations of sugar.nim=>
) #8679:untyped
makeLambda
inside each user of the lambdaproc callFun[Fun](fun: Fun)
:callFun(a~>a*10)
orcallFun(myTemplate)
etc, requiring 0 change in existing code (since the lambda can be passed as a generic)map
overly restrictive #8325 (see tests that implement show map, filter, join, toSeq, etc)iter it = myiter()
to allow perfect forwarding of iterators, with delayed evaluation #9374 ([RFC] first class inline iterators:iter it = myiter()
to allow perfect forwarding of iterators, with delayed evaluation): seelambdaIter
in unitteststoSeq
overload messes up iterator visibility RFCs#512 becausetoSeq
could be rewritten to be withoutuntyped
as argument to accept iterators, seetlambda_iterators.nim
; likewise, overload resolution (with typed and untyped) doesn't work with iterators #9219 could be closed since we can now pass the iterator as done in unittestsconst testForMe=alias assert
, ditto with aliasing generic/template/macro/overloadsconst my_echo=alias system.echo
worksmap
shouldn't care whether proc arg iscdecl
#8303 (see map defined intlambda_iterators.nim
)speed benefit
for example the following code gives a 3X to 4X speedup (with both debug build and
-d:danger
) over algorithms.isSorted which uses a callback closure viafunc isSorted*[T](a: openArray[T], cmp: proc(x, y: T): int {.closure.}, order = SortOrder.Ascending): bool =
other benefits
macro mydefine(name: untyped, val: typed)
can now become:const myname = mydefineAux(val)
; see testsalias: myecho=echo
to alias any symbol #11822 (alias: myecho=echo
)untyped
followed by callingbindSym
since we can now pass the symbol directly (either via an explicit generic paramfoo[T](fun: T)
or implicit genericfoo(fun: aliassym)
)typedesc
andstatic
without their restrictions (see Generic types as static parameters: type mismatch #9679) ; usingalias
you can also pass a type or a const (via passing a template symbol that returns that const, egmyConstReturningTemplate
or()~>myConstVal
); maybestatic/typedesc
could now be implemented in terms ofalias
which would fix bugs inherent with thesemixin
, for example this helps with issues like Generics "sandwiched" between two modules don't mixin their scope symbols properly #11225 (Generics "sandwiched" between two modules don't mixin their scope symbols properly)notes
[parser]
a => counter+=1
parsed as(a => counter) += 1
, violating the spec, and causing issues with => #8759 is a minor bug that affects bothsugar.=>
and this PR's~>
in some cases; should be easy to fixalias2
andaliassym
are placeholder names; I'll change these once we agree on final namesI'd prefer the syntax:
alias x = foo.bar
instead ofconst x = alias foo.bar
; this requires a minor parser change; it would simplify usage in general. The code in this PR wouldn't change much with this change, but user code would become simpler. If there's agreement, I'll switch toalias x = foo.bar
andproc bar(fun: alias)
compared to
sugar.=>
, the library code alias defined in this PR on top ofalias
are easier to use (doesn't suffer from type inference with lambdas doesn't work (requires explicit types) #7435 as shown in unittests), and more efficient (no function call overheard that would prevent inlining)will hide behind a feature flag (eg:
{.experimental: aliassym.}
) once we agree on name