OvermindDL1

OvermindDL1

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)

Showing Posts 1 to 10

OvermindDL1

OvermindDL1 OP

Cannot come up with a good syntax for an anonymous function, I was hoping for fun blah -> blah, especially as that quotes well by becoming [{:->, [], [[{:fun, [], [{:blah, [], Elixir}]}], {:blah, [], Elixir}]}], but putting it in a file (even inside a quoted context) causes a CompileError of unhandled operator ->, which is irritating… what is unhandled about it?! I’m trying to handle it… blah…

Similar separators are no good either, such as =>

I wonder why the inconsistency between a quote do fun blah -> blah end and a someMacro(fun blah -> blah), wonder if this is an Elixir compiler bug…

EDIT: Also, wtf does fn require an end regardless of any internal content in a macro, blah… I’ll probably just use fn, though awfully elixir with the weird trailing ‘end’ for no reason (considering there is only a single expression inside it)… Really really badly hate trailing turds (to use an erlang expression) like end’s for no reason though, even in Elixir… Whoever thought of (ruby creators? morons…) putting blocks in random places like an anonymous function with a single expression should go back to language design…

I’d very much like another idea on how to do function definitions in elixir’s syntax. ^.^

ericmj

ericmj

Elixir Core Team

There is not an inconsistency. Maybe you think you are doing fun(blah -> blah) but you are actually doing fun(blah) -> blah. -> can only exist inside a block. quote do fun blah -> blah end ← here the quote creates a block for you, but when you call a macro as someMacro(fun blah -> blah) you don’t have create a block.

Since the expression after -> is a block how do you know when to stop adding expressions to the block without end. For example:

list = Enum.map 1..10, fn num ->
  square = num * num
  square
IO.inspect(list)

Since we don’t have an end is the `IO.inspect part of the anonymous function or not?

OvermindDL1

OvermindDL1 OP

I also tried defml(fun x -> x) with the same operator error. ^.^

(EDIT: Also yesterday I tried this with an error too (defml supports single expression or do syntax both):

defml do
  fun x -> x
end

)

Exactly! The block should be explicit like it is everywhere else (with do/end). fn by default should have been a single expression element with an optional block (potentially also via do/end). The → could just be an infix operator that defines the left as the arguments and the right as the single expression (of which that expression could be a block), kind of like:

list = Enum.map 1..10, fn num -> num * num
IO.inspect(list)
# or
list = Enum.map 1..10, fn num -> do
  square = num * num
  square
  end
IO.inspect(list)
# or even:
list = Enum.map 1..10, fn num -> let square = num * num in square #  ^.^
IO.inspect(list)

Explicit is better than implicit after all. ^.^

Also, wtf magical inconsistency? o.O

/me really hates magically appearing blocks
Although I may be slightly biased considering every language I’ve very often used in the past does not have magically appearing blocks, from:

  • C/C++: Always delimited via {}, or leave it in many case for a single expression.
  • Python: Weird indentation, which is its own oddness, but is consistent at least.
  • Java: Ugh, same as C/C++ though.
  • Rust: Curly braces, optional in many cases when you only have a single expression too.
  • Erlang: The <expr>;<expr> is basically a let _ = <expr> in <expr>, when you stop it the ‘block’ ends, but it is not really a block, just a list of delimited expressions.

Etc… :slight_smile:

ericmj

ericmj

Elixir Core Team

I don’t see why you would get an error there. I just tried this in iex and it worked.

What is the magical inconsistency? It works because quote do fun blah -> blah end has a block. Do you see the do ... end? :slight_smile:

OvermindDL1

OvermindDL1 OP

Uhh, really? o.O?

(EDIT: Urp, my mistake, used fn here instead of fun, I’d prefer fn but it is less useful than fun due to auto-block magicness.)

Straight from one of my tests:

    defml do
      let id = fn x -> x in
      id 1
    end

Uncommenting it results in:

** (TokenMissingError) test/ml_elixir_test.exs:129: missing terminator: end (for "do" starting at line 1)
    (elixir) lib/code.ex:370: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

Simplifying it to:

    defml do
      fn x -> x
    end

Results in an identical error.

Why yes, I see a block here:

    defml do
      fn x -> x
    end

And I get an end missing. :slight_smile:
And interestinyl replacing defml with quote to become:

    quote do
      fn x -> x
    end

In the test file also fails with an identical error… o.O

Just commented those lines and the test file passes again so it is not the file causing the issue… Trying from iex now:

iex> import MLElixir
MLElixir
iex> defml fn x -> x
...>
...> end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> defml(fn x -> x)
** (SyntaxError) iex:3: "fn" is missing terminator "end". unexpected token: ")" at line 3

iex> defml do fn x -> x end
...>
...>
...>
...>
...> end
#Function<6.52032458/1 in :erl_eval.expr/5>
iex> defml(do: fn x -> x)
** (SyntaxError) iex:4: "fn" is missing terminator "end". unexpected token: ")" at line 4

iex> quote(do: fn x -> x)
** (SyntaxError) iex:4: "fn" is missing terminator "end". unexpected token: ")" at line 4

iex> quote do fn x -> x end
...> end
{:fn, [], [{:->, [], [[{:x, [], Elixir}], {:x, [], Elixir}]}]}

And yet trying to use the original syntax that I wanted:

iex> quote do fun x -> x end
[{:->, [], [[{:fun, [], [{:x, [], Elixir}]}], {:x, [], Elixir}]}]

Hmm, so quote takes this syntax fine, let’s dump it into defml then:

iex> defml fun x -> x
** (SyntaxError) iex:6: syntax error before: '->'

iex> defml(fun x -> x)
** (SyntaxError) iex:6: syntax error before: '->'

iex> defml(do: fun x -> x)
** (SyntaxError) iex:6: syntax error before: '->'

Hence here is the wtf. ^.^

EDIT: Think I may have come up with a fairly consistent way that gets rid of the end oddness when you only have a single expression…

Which brings up, is there a construct in elixir (not an anonymous function as those have overhead) where you can make a new variable bindings? case is apparently not it because it does a really stupid thing where:

a = 42
b = case blah() do
    :ok -> a = bloop() <> "world"
      {:ok, a}
    :error -> :error
  end
# Wtf `a` here is `bloop() <> "world"` *or* `42` instead of just always 42?!?  How does a block
# not sanitize variables?!  Definitely not like C++ where every block is a new subscope...

I think I’m just going to have to decorate every-single-variable with a trailing number or something… Some things in the Elixir AST just do not make sense >.<

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 OP

The macro itself can wrap it though, it is just at the compiler level before the macro is ever hit… :-/

So… this?

iex> import MLElixir
MLElixir
iex> defml(fun x -> x)
** (SyntaxError) iex:2: syntax error before: '->'
iex> defml do fun x -> x end
:test_ok

So it does not seem like just sugar? O.o?

ericmj

ericmj

Elixir Core Team

The compiler raises with a syntax error which means it failed to parse the syntax. The parser does not expand macros or execute elixir code so it does not matter what you do in your macro. All code still has to be proper Elixir syntax regardless if we have macros.

Those are argument parenthesis. Try this: defml((fun x -> x)).

OvermindDL1

OvermindDL1 OP

Was just about to edit my last post, already tried that. :slight_smile:

Here is my edit content:

So it seems a function call does not scope inside it, this is such a weird syntax (I understand ‘how’ it is working the way it is, but not ‘why’ it was initially created this way…):

iex> defml (fun x -> x)
:test_failed
iex> defml((fun x -> x))
:test_failed
iex> defml(fun x -> x)
** (SyntaxError) iex:2: syntax error before: '->'

However, an issue here is the ‘:test_failed’ response that I am printing, that means it got nil as the incoming AST, and indeed that is what I get for those if I print out my debug steps instead:

iex> defml (fun x -> x)
{:ML, nil}
{:MLAST, {:"$$LIT$$", [type: {:"$$TCONST$$", :atom, [values: [nil]]}], nil}}
{:MLENV,
 %MLElixir.MLEnv{counter: -1,
  funs: %{+: #Function<0.36760603/3 in MLElixir.Core.__ml_open__/0>},
  type_bindings: %{}, type_funs: %{}, type_vars: %{}}}
{:MLDONE, nil}
nil
iex> defml((fun x -> x))
{:ML, nil}
{:MLAST, {:"$$LIT$$", [type: {:"$$TCONST$$", :atom, [values: [nil]]}], nil}}
{:MLENV,
 %MLElixir.MLEnv{counter: -1,
  funs: %{+: #Function<0.36760603/3 in MLElixir.Core.__ml_open__/0>},
  type_bindings: %{}, type_funs: %{}, type_vars: %{}}}
{:MLDONE, nil}
nil
iex> defml 42
{:ML, 42}
{:MLAST, {:"$$LIT$$", [type: {:"$$TCONST$$", :int, [values: '*']}], 42}}
{:MLENV,
 %MLElixir.MLEnv{counter: -1,
  funs: %{+: #Function<0.36760603/3 in MLElixir.Core.__ml_open__/0>},
  type_bindings: %{}, type_funs: %{}, type_vars: %{}}}
{:MLDONE, 42}
42
iex> defml let a = 42 in a
{:ML,
 {:let, [line: 5],
  [{:=, [line: 5],
    [{:a, [line: 5], nil}, {:in, [line: 5], [42, {:a, [line: 5], nil}]}]}]}}
{:MLAST,
 {:"$$LET$$", [type: {:"$$TPTR$$", 0, []}, line: 5],
  [{:"$$VAR$$", [type: {:"$$TPTR$$", 0, []}, line: 5], [:a, nil]},
   {:"$$LIT$$", [type: {:"$$TCONST$$", :int, [values: '*']}], 42},
   {:"$$VAR$$", [type: {:"$$TPTR$$", 0, []}, line: 5], [:a, nil]}]}}
{:MLENV,
 %MLElixir.MLEnv{counter: 0,
  funs: %{+: #Function<0.36760603/3 in MLElixir.Core.__ml_open__/0>},
  type_bindings: %{a: {:"$$TPTR$$", 0, []}}, type_funs: %{},
  type_vars: %{0 => {:"$$TCONST$$", :int, [values: '*']}}}}
{:MLDONE,
 {:__block__, [type: {:"$$TPTR$$", 0, []}, line: 5],
  [{:=, [], [{:a, [type: {:"$$TPTR$$", 0, []}, line: 5], nil}, 42]},
   {:a, [type: {:"$$TPTR$$", 0, []}, line: 5], nil}]}}
42

And yet:

iex> quote(do: (fun x -> x))
[{:->, [], [[{:fun, [], [{:x, [], Elixir}]}], {:x, [], Elixir}]}]

I’m… confused again… o.O

And for note, the :ML tuple is the first thing listed:


  defmacro defml(opts) when is_list(opts) do
    defml_impl(opts[:do], opts)
  end
  defmacro defml(expr) do
    defml_impl(expr, [])
  end

  defp defml_impl(expr, opts) do
    IO.inspect {:ML, expr}
# ...
ericmj

ericmj

Elixir Core Team

Here’s your bug ^. Your macro receives something like: ast = [{:->, [], [[{:fun, [], [{:x, [], Elixir}]}], {:x, [], Elixir}]}]. ast[:do] == nil because you have no :do block there.

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews