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.