OvermindDL1

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)
132 13487 106

Most Liked

derpydev

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

josevalim

Creator of Elixir

Reading this thread has been such an emotional rollercoaster. :joy:

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

ericmj

Elixir Core Team

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

OvermindDL1

OvermindDL1

Oh I just caught that myself too! That is brilliant. ^.^

EDIT:
Vwoop:

  defmacro defml(opts) when is_list(opts) do
    case opts[:do] do
      nil -> defml_impl(opts, [])
      ast -> defml_impl(ast, opts)
    end
  end

I can probably live with requiring parenthesis around anonymousall function definitions, though would be nice if an infix operator like => (much as I stylistically dislike fat arrows) would work like normal infix operators (-> is really weird… not like a normal operator…). :slight_smile:

EDIT: What no strikethrough markdown operator on this forum? o.O

EDIT: For note, I’m trying to translate a mini-language I made into Elixir’s AST to use straight in Elixir (right now it parses out text into elixir ast, which makes syntax coloring really bad when used like defml "let a = 42 in a", although it works, just… ugly, we need read-macro’s in elixir ^.^), where its function syntax is:

let myAdder = fun
  | (a:int) (b:int b>=0) -> a + b
  | (a:int) (b:int) -> a - b
  | (a:binary) (b:binary) -> String.concat [a, b]
  | a b -> error "unsupported"
in myAdder 10 (-4)

Types are optional if it can be inferred, though fully typed it would look more like:

let myAdder = fun
  | (a : int) (b : int b>=0) : int a+b -> a + b (* Since a has a valid range of infinite here, the return type is infinite+{b's range, which is {0, infinite}}, which is still infinite regardless, it is not that smart yet *)
  | (a : int) (b : int) : int a-b -> a - b (* int's extra information holds possible values allowed, simple math allowed on those values *)
  | (a : string) (b : string) : string -> String.concat [a, b] (* string's can refine on length only, currently *)
  | (a : any) (b : any) -> error "unsupported" (* any's do not have any refinement yet *)
in let curried = myAdder "Hello " (* A new erlang anonymous function is made here that curries myAdder, curried things and closures cannot be made into top-level module functions in this play language (type error) *)
in curried "world" (* Now I call it *)
josevalim

josevalim

Creator of Elixir

Yes, yes! I didn’t mean it as a negative thing. More like: “will he make it?!”.

The reason we didn’t use tokenization macros is because they are quite more powerful than macros, allowing anyone to create their own syntax for Elixir. It would be wonders for what you are doing but could make Elixir a really hard language, as totally new syntax constructs could be introduced any time.

Where Next?

Popular in Libraries Top

deadtrickster
I’ve just released stable versions of my Prometheus Elixir libs: Elixir client [docs]; Ecto collector [docs]; Plugs instrumenter/Export...
New
oltarasenko
Dear Elixir community, After a year of development, bug fixes, and improvements, we are proudly ready to share the release of Crawly 0.1...
New
sabiwara
Dune is a sandbox for Elixir and aims to safely evaluate user-provided code. You can try it out using this basic Elixir playground made ...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
tmbb
I’ve published the first version of my Makeup library. It’s a syntax highlighter for Elixir in the spirit of Pygments, Currently it highl...
New
tmbb
PhoenixWS - Websockets over Phoenix Channels Source code on Github here: https://github.com/tmbb/phoenix_ws Phoenix channels are a great...
New
OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
tmbb
I’ve decided to create this topic to discuss optimization possibilities for something like Phoenix LiveView. I’ve created this topic unde...
New
gjaldon
As the title states, EctoEnum has just been updated after some time of hardly any activity in the repo. Here’s the latest release: https:...
New
Hal9000
Here is my first stab at this. README pasted below. https://github.com/Hal9000/elixir_random Comments and critiques are welcome. Th...
New

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Sub Categories:

We're in Beta

About us Mission Statement