Created
July 29, 2016 20:51
-
-
Save ebb/dacc0adf10b8f9e620cb86d5198aea45 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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