You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closures are a pretty essential feature for things like iterator adapters. There are a few considerations in mind when it comes to closures:
How do we make them not require the heap? I'd rather have closures be quite cheap in cost.
One possible approach is to take Rust's compiler-generated opaque type idea, where to use closures, you need to create a generic parameter that allows for any type to be passed, or optionally, use trait pointers.
I think we should allow for explicitly preventing closures from storing pointers, as it often causes more trouble than it's worth (lifetime hell). Or maybe create two constraints, fun and fun rc, the first of which permits any variables but acts like a pointer (can only be used as an argument and not stored anywhere), the second only permits functions that do not capture external variables by reference (ie. the latter can be stored in an rc).
I'm not a fan of fun rc because, with its primary purpose being storage in rcs, rc fun rc (Float): Float does not look very pretty. Maybe rc fun (Float): Float should imply that the inner fun is rc?
There needs to be a way of specifying that variables should be moved into the closure, explicitly.
Here's a preview of what such closure constraints would look like.
fun map[T, U, F](sequence: Seq[T], fn: F): Seq[U]
where
F: fun (T): U
var result = Seq[U].with_capacity(sequence.len)
for element in sequence.iter_move
val mapped = fn(element)
result.push(mapped)
result
The above defines a function map, which accepts the argument fn, whose type is F which must satisfy fun (T): U. Now, each function satisfies the fun (T): U constraint, but all values that are only known to have said constraint act like pointers in terms of lifetimes, so we can work with them, pass them by argument, return them, but not store them in external locations.
Additionally, we need to define syntax for creating closures. I propose the following:
# No return type and no arguments:
var my_function = fun
print("x")
# With an argument with an explicit type:
var my_function = fun (x: Float)
print(x + 2)
# With an argument with an explicit parameter type _and_ return type:
var my_function = fun (x: Float): Float
print(x + 2)
x + 2
# With inferred parameter type, single-line version using `->`:
[1, 2, 3]
.iter_move
.for_each(fun (x) -> print(x))
# With inferred parameter and return type, again single-line version:
[1, 2, 3]
.iter_move
.map(fun (x) -> x + 2)
Moving values into the closure is done by adding the move keyword after fun, like fun move -> x (a closure without arguments, moving all captured variables, in this case x, into its body).
Indentation-based syntax does not play well with this version of creating closures:
do_stuff(fun ()
print("abc")
)
leaving an ugly trailing parenthesis at the end. Thus I also propose a syntactic sugar to implement later:
do_stuff() do ()
print("abc")
Dubbed "infix do blocks", it moves the last closure argument out of the parentheses, into a separate block outside of the function, introduced through an infix operator. Alternatively, to not overload the do keyword with two meanings, fun could also be used.
do_stuff() fun ()
print("abc")
The text was updated successfully, but these errors were encountered:
Closures are a pretty essential feature for things like iterator adapters. There are a few considerations in mind when it comes to closures:
fun
andfun rc
, the first of which permits any variables but acts like a pointer (can only be used as an argument and not stored anywhere), the second only permits functions that do not capture external variables by reference (ie. the latter can be stored in anrc
).fun rc
because, with its primary purpose being storage inrc
s,rc fun rc (Float): Float
does not look very pretty. Mayberc fun (Float): Float
should imply that the innerfun
isrc
?Here's a preview of what such closure constraints would look like.
The above defines a function
map
, which accepts the argumentfn
, whose type isF
which must satisfyfun (T): U
. Now, each function satisfies thefun (T): U
constraint, but all values that are only known to have said constraint act like pointers in terms of lifetimes, so we can work with them, pass them by argument, return them, but not store them in external locations.Additionally, we need to define syntax for creating closures. I propose the following:
Moving values into the closure is done by adding the
move
keyword afterfun
, likefun move -> x
(a closure without arguments, moving all captured variables, in this casex
, into its body).Indentation-based syntax does not play well with this version of creating closures:
leaving an ugly trailing parenthesis at the end. Thus I also propose a syntactic sugar to implement later:
Dubbed "infix
do
blocks", it moves the last closure argument out of the parentheses, into a separate block outside of the function, introduced through an infix operator. Alternatively, to not overload thedo
keyword with two meanings,fun
could also be used.The text was updated successfully, but these errors were encountered: