From 36a7b4dba38cc4f0572dca195194bbbf8643ba51 Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Wed, 28 Jun 2023 15:51:45 -0600 Subject: [PATCH] Added strawman designs --- strawman1.txt | 54 ++++++++++++++++++++++++++++++ strawman2.txt | 63 +++++++++++++++++++++++++++++++++++ strawman3.txt | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++ strawman4.txt | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 strawman1.txt create mode 100644 strawman2.txt create mode 100644 strawman3.txt create mode 100644 strawman4.txt diff --git a/strawman1.txt b/strawman1.txt new file mode 100644 index 0000000..c99785f --- /dev/null +++ b/strawman1.txt @@ -0,0 +1,54 @@ +# 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) = + # 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_dont_care_about() = print("ignore me") + in {empty, insert, find} + +# Prints "`some(2)". +def do_tree_things() = + # Assume that int_compare is in the stdlib or something. + # Notice that we can ignore a record field. I want to make record + # syntax basically the same as in JS. + let {insert, empty, find, ...stuff_we_dont_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. + and _ = empty |> insert(_, 5) |> insert(_, 2) |> insert(_, 10) + |> find(2) |> print + in stuff_we_dont_need + +# Prints "`some(2)\nignore me". +def main() = do_tree_things().func_we_dont_care_about() + + +# I'd better be able to make type inference work, or else the user might have +# to annotate binary_tree like this: +binary_tree : fn('a, 'a) -> Record{ + empty: recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, + insert: fn(recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, 'a) -> + recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, + find: fn(recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, 'a) -> + Union{some of 'a, nothing}, + func_we_dont_care_about: fn() -> Record{} +} +# Though it might be possible to introduce a way to factor out the tree type, +# which is the big thing starting with "recurse T". \ No newline at end of file diff --git a/strawman2.txt b/strawman2.txt new file mode 100644 index 0000000..9afd260 --- /dev/null +++ b/strawman2.txt @@ -0,0 +1,63 @@ +# 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? + +# 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. +binary_tree(compare) = + # Backticks here indicate that leaf is a constructor for a tagged union + # (variant) type. + let + empty = `leaf + 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}) + 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) + func_we_dont_care_about() = print("ignore me") + in {empty, insert, find} + +# Prints "`some(2)". +do_tree_things() = + # Assume that int_compare is in the stdlib or something. + # Notice that we can ignore a record field. I want to make record + # syntax basically the same as in JS. + let {insert, empty, find, ...stuff_we_dont_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_dont_need + +# Prints "`some(2)\nignore me". +def main() = do_tree_things().func_we_dont_care_about() + + +# I'd better be able to make type inference work, or else the user might have +# to annotate binary_tree like this: +binary_tree : fn('a, 'a) -> Record{ + empty: recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, + insert: fn(recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, 'a) -> + recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, + find: fn(recurse T in Union{leaf, branch of Record{left: T, value: 'a, right: T}, 'a) -> + Union{some of 'a, nothing}, + func_we_dont_care_about: fn() -> Record{} +} +# Though it might be possible to introduce a way to factor out the tree type, +# which is the big thing starting with "recurse T". \ No newline at end of file diff --git a/strawman3.txt b/strawman3.txt new file mode 100644 index 0000000..1e6618f --- /dev/null +++ b/strawman3.txt @@ -0,0 +1,91 @@ +#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 = {} +binary_tree : fn('a, 'a) -> { + empty: tree 'a, + insert: fn(tree 'a, 'a) -> tree 'a, + find: fn(tree 'a, 'a) -> maybe 'a, + func_we_dont_care_about: fn() -> 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. +binary_tree(compare) = + # Backticks here indicate that leaf is a constructor for a tagged union + # (variant) type. + let + # 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. + empty = `leaf + 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}) + 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) + func_we_dont_care_about() = print("ignore me") + in {empty, insert, find} + +# Prints "`some(2)". +do_tree_things() = + # Assume that int_compare is in the stdlib or something. + # Notice that we can ignore a record field. I want to make record + # syntax basically the same as in JS. + let {insert, empty, find | stuff_we_dont_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_dont_need + +# Prints "`some(2)\nignore me". +main() = do_tree_things().func_we_dont_care_about() + +annotate: fn('a, {|'b}) -> {note: 'a | 'b} +annotate(note, obj) = { note | obj } + +deannotate: fn({note: 'a | 'b}) -> {|'b} +deannotate(r) = let {note=_ | rest} = r in rest + +reannotate: fn('a, {note: 'a | 'b}) -> {note: 'a | 'b} +reannotate(note, obj) = {|obj with note=note} # or maybe field punning: {|obj with note} + +map: fn(fn('a) -> 'b, ['a]) -> ['b] + +doubled = let double(n) = 2 * n in map(double, [1,2,3,4,5]) \ No newline at end of file diff --git a/strawman4.txt b/strawman4.txt new file mode 100644 index 0000000..6b0ec27 --- /dev/null +++ b/strawman4.txt @@ -0,0 +1,90 @@ +# v4 +# Moved type annotations into bindings. +# Added def keyword for top-level bindings. +# Added and keyword to multiple-binding let-expressions. + +# 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 = {} + +# 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_dont_care_about: fn() -> 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_dont_care_about() = print("ignore me") + in {empty, insert, find, func_we_dont_care_about} + +# Prints "`some(2)". +def do_tree_things() = + # Assume that int_compare is in the stdlib or something. + let {insert, empty, find | stuff_we_dont_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_dont_need + +# Prints "`some(2)\nignore me". +def main() = do_tree_things().func_we_dont_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]) \ No newline at end of file