Skip to content

Instantly share code, notes, and snippets.

@ebb
Created July 29, 2016 20:51
Show Gist options
  • Save ebb/dacc0adf10b8f9e620cb86d5198aea45 to your computer and use it in GitHub Desktop.
Save ebb/dacc0adf10b8f9e620cb86d5198aea45 to your computer and use it in GitHub Desktop.
Notes on infix operators and function application in Language 84
Language 84 is an expression-oriented language with first-class functions and
immutable data.
The first thing to note is that expressions using infix operators or function
application are always parenthesized in Language 84. There are two motivations
for that choice:
1. It allows the language to get by with fewer separators and terminators
(",", "then", "else", "end", ";;").
2. It allows the language to have a relatively clean look even without
indentation-sensitivity.
As I write this note, Language 84 has a small set of built-in binary operators:
+ - * < > = <= >= : (int -> int -> int)
:: : (All a. a -> (list a) -> (list a))
It also has two built-in unary operators:
~ : (int -> int)
! : (bool -> bool)
The two unary operators are both "negation" operators for their type. [1]
Note that there is no overloading at all.
I'm not planning on introducing overloading into the language. Instead, I'm
working toward a design that allows lexical binding of operator symbols.
The motivation for such operator binding is not only to allow shadowing of
definitions but also to allow introduction of operators that are not built in.
Here's an example in which the >>= operator is locally bound to be the bind
operator of the identity monad.
(Example based on one from the Elixir documentation.)
Let (x >>= f) (f x)
In
([1 [2] 3] >>= LIST.flatten >>= (ENUM.map (Func x. (x * 2))))
And here we use a slightly different binding form to bind |> to be the function
IDENTITY.bind.
Let (|>) IDENTITY.bind
In
([1 [2] 3] |> LIST.flatten |> (ENUM.map (Func x. (x * 2))))
I've decided to omit the usual mapping from operators to their associativity
and precedence.
Instead, I'm introducing the following rules:
1. Operators always associate to the left by default. Right-associativity
can be specified locally by using the keyword "Right" at the start of
the expression:
(a + b + c) = ((a + b) + c)
(Right i :: j :: k :: indices) = (i :: (j :: (k :: indices)))
2. Roughly speaking, the use of different operators within an expression
must be separated by explicit grouping. This rule make precedence a
non-issue.
(a * 10 + b + c) not allowed
((a * 10) + b + c) allowed
((a * 10) + b + (c - d)) allowed
In summary, I'm trading off concision for flexibility and clarity. A goal
is to eliminate moments of confusion like, "Am I remembering the
precedence rules correctly here?" and "Where can I find the definition of
this operator for this pair of types?".
Function application is treated as a blank infix operator. Hence:
(f a b) = ((f a) b)
(Right f g x) = (f (g x))
(f x :: xs) not allowed
((f x) :: xs) allowed
Is overloading really essential? So many languages take the overloading that's
commonly used for operators and extend it to functions bound to identifiers.
I'm trying the opposite approach: take the lexical binding that's commonly used
for functions bound to identifiers and extend that mechanism to the world of
operators.
[1] A footnote on subtraction and negation:
Like Standard ML, I use ~ for negation instead of -. The reason is that
when you try to use the same symbol for both, the following expression
becomes ambiguous:
(h - x)
Does it mean, "apply function h to argument negative x" or, "subtract x
from h"?
Haskell and Ocaml treat it as subtraction and require the use of
parentheses to get the other interpretation:
(h (-x))
F# and Elm both decide based on whether there is whitespace between the "-"
and the x.
(h - x) subtract x from h
(h -x) apply h to negative x
Another option is to use "-" only for negation and force the programmer to
write the following instead of using a subtraction operator:
(h + -x)
I tried that for a while but found it awkward. (Subtraction is really
common.)
It's a curious issue that forces you to find a trade-off based on
simplicity/consistency of semantics, familiarity, and readability. The
traditional syntax for function application makes things easier here:
f(h - x, h(-x))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment