MLElixir - attempting an ML-traditional syntax entirely within the Elixir AST

Little more work, a large revamp of how the code works (more shared code with Typed Elixir whoo!). Module types are a thing now, and consequently so are accessing remote type, and thus also made a defmlmodule call, all of this works and dies if the types do not match as expected:



import MLElixir
defmlmodule MLModuleTest do

  type type_declaration

  type type_definition = integer

  def test_int_untyped = 42
  def test_int_typed | integer = 42
  def test_float_untyped = 6.28
  def test_float_typed | float = 6.28
  def test_defined | type_definition = 42
  def identity_untyped(x) = x
  def identity_typed(x | +id_type) | +id_type = x
  def identity_int(x | integer, _f | float) | integer = x
  def identity_float(_x | integer, f | float) | float = f
  def test_block0 do 42 end
  def test_blockN0() do 42 end
  def test_blockT0() | integer do 42 end
  def test_blockN1(x) do x end
  def test_blockT1(x | +id_type) | +id_type do x end

end


defmlmodule MLModuleTest_Generalized do
  type t

  def blah(t | t) | t, do: t
end


defmlmodule MLModuleTest_Specific do
  type t = MLModuleTest.type_definition
  type Specific = MLModuleTest_Generalized.(t: float)

  def testering0 | t = 42
end

Parameterized types and overloadable types are done via .(blah) syntax. So to refine a type (say a ‘Dict’ module has a type ‘t’ that is generic, you can refine it to a specific type that fits within generic (anything) and use the module with that kind of type) you specify it with the standard proplist style with the key as the id, so in the examples above that is the type specific = MLModuleTest_Generalized.(t: float) call, it refines the t type on MLModuleTest_Generalized to make a more refined type. You can then call on Specific to access it but with the refined type, however I’m not quite happy with this syntax (not able to ‘pass’ a module around), so going to change it sometime when I get time so that modules can be packed as a value like in OCaml, pretty easy to do on the BEAM. ^.^

However, the same syntax will also be used to apply types as well, so given a type like:

type result(ok, error) = enum # still unsure what to call this type, variant is long...
| ok = ok
| error = error

You can refine it like result.(String.t, nil) or like result.(ok: String.t, error: nil) for explicitness. ^.^

The . (dot) calling convention is used for applying types, the normal (or left out) parenthesis is for function application. Still up in the air of course.

EDIT: Using ‘def’ because syntax coloring, ‘let’ is supported too, and it supports 'let’ish style and elixir do/block style.

Also, a type name like integer refers to a built-in or pre-named type, a type name prefixed with + like +id_type is a named generic.

2 Likes