# v5 # Added implicit (dynamic) bindings # v4 # Moved type annotations into bindings. # Added def keyword for top-level bindings. # Added and keyword to multiple-binding let-expressions. # Added arbitrary strings as identifiers. # v3 # Added type aliases. # Added some row variable examples. # Added array type # TODO Add optional types # TODO Get rid of ambiguous syntax at end of type alias definition. # TODO Try out arbitrary strings as field names # This should be fine without any extra syntax, as long as we don't allow pattern-matching primitive values. # For consistency, especially re: punning, we'd need to also allow this for normal identifiers, which would need # new syntax. # TODO Modules (files) and opaque types # TODO More primitive types # v2 # I realized/remembered that I don't need `def`, `and`, or commas. Because function # application isn't by juxtapositon, I can look ahead by one token to know # whether the expression is complete. # If the language is impure, it also needs a sequencing operator. Let's use ; # because why not? type alias tree 'a = recurse 'T in type alias maybe 'a = type alias unit = {} implicit print(s: string) -> unit # Type aliases should appear in compiler messages. # binary_tree takes a comparison function and returns a record (like a JS # object) as a module for handling binary trees of values of the same type # as compare's arguments. def binary_tree(compare: fn('a) -> 'a) -> { empty: tree 'a, insert: fn(tree 'a, 'a) -> tree 'a, find: fn(tree 'a, 'a) -> maybe 'a, @"func we don't care about": fn() -> |print| unit } = # A let expression can bind multiple names. Functions have access to all names within the expression, # while other values only have access to names bound before them. # This is to prevent nonsensical mutual recursion between non-function values. # Backticks here indicate that leaf is a constructor for a tagged union (variant) type. let empty = `leaf and insert(tree, value) = when tree is `leaf => `branch({left=`leaf, value = value, right=`leaf) `branch({left, value=value2, right}) => when compare(value, value2) is `eq => `branch({left, value, right}) `lt => let new_left = insert(left, value) in `branch({left=new_left, value=value2, right}) `gt => let new_right = insert(right, value) in `branch({left, value=value2, new_right}) and find(tree, needle) = when tree is `leaf => `nothing `branch({left, value, right}) => when compare(needle, value) is `eq => `some(value) `lt => find(left, needle) `gt => find(right, needle) and @"func we don't care about"() = print("ignore me") in {empty, insert, find, @"func we don't care about"} # Prints "`some(2)". def do_tree_things() -> |print| unit = # Assume that int_compare is in the stdlib or something. let {insert, empty, find | @"stuff we don't need"} = binary_tree(int_compare) # I invented this fancy partial-application syntax with _, and then found # that Scala does it the same way. Also note the pipe operator from Elm. in empty |> insert(_, 5) |> insert(_, 2) |> insert(_, 10) |> find(2) |> print; @"stuff we don't need" # Prints "`some(2)\nignore me". def main() = with print = native_print in do_tree_things().@"func we don't care about"() def annotate(note: 'a, obj: {|'b}) -> {note: 'a | 'b} = { note | obj } def deannotate(r: {note: 'a | 'b}) -> {|'b} = let {note=_ | rest} = r in rest def reannotate(note: 'a, obj: {note: 'a | 'b}) -> {note: 'a | 'b} = {|obj with note=note} # or maybe field punning: {|obj with note} def map(f: fn('a) -> 'b, xs: ['a]) -> ['b] = crash("TODO") def doubled = let double(n) = 2 * n in map(double, [1,2,3,4,5])