finn-lang/strawman4.txt

91 lines
3.5 KiB
Plaintext
Raw Normal View History

2023-06-28 21:51:45 +00:00
# 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.
2023-06-28 21:51:45 +00:00
# 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 <leaf, branch: {left: 'T, value: 'a, right: 'T}>
type alias maybe 'a = <some: 'a, nothing>
type alias 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() -> unit
2023-06-28 21:51:45 +00:00
} =
# 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"}
2023-06-28 21:51:45 +00:00
# Prints "`some(2)".
def do_tree_things() =
# Assume that int_compare is in the stdlib or something.
let {insert, empty, find | @"stuff we don't need"} = binary_tree(int_compare)
2023-06-28 21:51:45 +00:00
# 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"
2023-06-28 21:51:45 +00:00
# Prints "`some(2)\nignore me".
def main() = do_tree_things().@"func we don't care about"()
2023-06-28 21:51:45 +00:00
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])