OvermindDL1
MLElixir - attempting an ML-traditional syntax entirely within the Elixir AST
Been making an MLElixir thing (not released yet…) for fun in spare time in the past day. I’m just trying to see how much I can get an ML-traditional syntax entirely within the Elixir AST, while being properly typed (with occasional fun with Refined Types and such). An example IEX session with it:
- Basic Types (adding more over time)
iex> import MLElixir
MLElixir
iex> defml 1
1
iex> defml 6.28
6.28
iex> defml :ok
:ok
- Let untyped variable bindings:
iex> defml let _a = 2 in 1
1
iex> defml let a = 1 in a
1
iex> defml let a = 42 in
...> let b = a in
...> let c = b in
...> c
42
- Let Typed variable bindings (The errors are very simplistic and not descriptive right now, still debugging time after all):
iex> defml let ![a: int] = 1 in a
1
iex> defml let ![a: float] = 6.28 in a
6.28
iex> defml let a = 1 in
...> let ![b: int] = a in
...> b
1
iex> defml let ![a: int] = 6.28 in a
** (MLElixir.UnificationError) Unification error between `{:"$$TCONST$$", :float, [values: [6.28]]}` and `{:"$$TCONST$$", :int, []}` with message: Unable to resolve mismatched types
(typed_elixir) lib/ml_elixir.ex:566: MLElixir.resolve_types!/3
(typed_elixir) lib/ml_elixir.ex:250: MLElixir.resolve_binding/3
(typed_elixir) lib/ml_elixir.ex:163: MLElixir.parse_let/3
(typed_elixir) expanding macro: MLElixir.defml/1
iex:16: (file)
- Let Refined Typed variable bindings:
iex> defml let ![a: int a=1] = 1 in a
1
iex> defml let ![a: int a<=2] = 1 in a
1
iex> defml let ![a: int a>=2] = 1 in a
** (MLElixir.UnificationError) Unification error between `{:"$$TCONST$$", :int, [values: [1]]}` and `{:"$$TCONST$$", :int, [values: [{2, :infinite}]]}` with message: Unable to resolve
(typed_elixir) lib/ml_elixir.ex:566: MLElixir.resolve_types!/3
(typed_elixir) lib/ml_elixir.ex:250: MLElixir.resolve_binding/3
(typed_elixir) lib/ml_elixir.ex:163: MLElixir.parse_let/3
(typed_elixir) expanding macro: MLElixir.defml/1
iex:6: (file)
Function calls (shown here via +):
iex> defml 1+2
3
iex> defml 1.1+2.2
3.3000000000000003
iex> defml 1+2.2
** (MLElixir.UnificationError) Unification error between `{:"$$TCONST$$", :int, [values: [1]]}` and `{:"$$TCONST$$", :float, [values: [2.2]]}` with message: Unable to unify types
(typed_elixir) lib/ml_elixir.ex:712: MLElixir.unify_types!/3
(typed_elixir) lib/ml_elixir.ex:108: anonymous fn/3 in MLElixir.Core.__ml_open__/0
(typed_elixir) lib/ml_elixir.ex:199: MLElixir.parse_ml_expr/2
(typed_elixir) lib/ml_elixir.ex:145: MLElixir.defml_impl/2
(typed_elixir) expanding macro: MLElixir.defml/1
iex:2: (file)
Opening (‘import’ in Elixir parlance) another module (also showing how to disable the Core opens, as you can see it is the Core that defines the + function):
iex> defml let open MLElixir.Core in 1+2
3
iex> defml no_default_opens: true, do: let open MLElixir.Core in 1+2
3
iex> defml no_default_opens: true, do: 1+2
** (MLElixir.InvalidCall) 6:Invalid call of `+` because of: No such function found
(typed_elixir) lib/ml_elixir.ex:196: MLElixir.parse_ml_expr/2
(typed_elixir) lib/ml_elixir.ex:145: MLElixir.defml_impl/2
(typed_elixir) expanding macro: MLElixir.defml/1
iex:6: (file)
Most Liked
derpydev
Sorry to be a bit off topic, but I feel like you missed an great opportunity in not calling this project ExML…ok I’ll show myself out now…
Seriously though awesome project!
josevalim
Reading this thread has been such an emotional rollercoaster. 
To sum up, -> is only allowed between do/end, fn/end and (/). But you need to be careful because the parens need to apply to the -> and not arguments. The reason why foo(fun x -> x) does not work becomes clearer if you add multiple arguments. If you have foo(fun x, y -> x), does it mean foo(fun x, (y -> x)) or foo((fun x, y -> x))?
The reason I wrote the document linked by Eric is exactly because we wanted to show it is less rules than most would expect. Especially because we don’t need to specify the rules for keywords like case, def, defmodule, receive, if, try, etc.
ericmj
The difference between quote do fun x -> x end and defml fun x -> x is exactly the block. quote is not magically cheating in any way, the parser accepts -> in quote because it’s inside a block. When you call your defml macro you don’t wrap -> in a block.
If you use fn x -> x instead of fun x -> x you need to also end the block with an end since fn creates a block just like do.
It’s easy to remember what creates blocks in Elixir because there are only three ways to do it. do ... end, fn ... end and ( ... ). do blocks are in fact just sugar for ( ... ) and they are represented the same way in the syntax tree.
Check the “Blocks” section in the docs [1] for more details.
[1] https://hexdocs.pm/elixir/master/syntax-reference.html#syntax-sugar







