tsuki-lang / tsuki

An elegant, robust, and efficient programming language, that just lets you get things done.
MIT License
29 stars 2 forks source link

Closures #12

Open liquidev opened 2 years ago

liquidev commented 2 years ago

Closures are a pretty essential feature for things like iterator adapters. There are a few considerations in mind when it comes to closures:

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")