Giter Club home page Giter Club logo

alpaca's People

Contributors

arpunk avatar danabr avatar erszcz avatar getong avatar j14159 avatar jkakar avatar lepoetemaudit avatar licenser avatar lpil avatar monkeygroover avatar nobbz avatar shalokshalom avatar tjweir avatar ypaq avatar yurrriq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

alpaca's Issues

Fails to compile when applying the result of a function

When a function returns a function, it cannot be applied (in MLFE - from Erlang, the resulting function can be called).

module curry_fun

export curried/1

curried arg1 =
  let curried2 arg2 =
    let curried3 arg3 = 
      arg1 + arg2 + arg3
    in curried3  
  in
    curried2

From Erlang, it works fine:

2> (((curry_fun:curried(1))(2))(3)).
6

But in MLFE, this won't compile: curried 1 2 3 resulting in a compiler error:

mlfe_typer:type_modules/2 crashed with error:{error,
                                              {arity_error,curry_fun,11}}

Using parens (to prevent what I presume is greedy application above) produces a different error:

v = ((curried 1) 2) 3

-------

mlfe_typer:type_modules/2 crashed with error:function_clause

Double line breaks in expressions fail to compile

It would be desirable to be able to split code up with several line breaks. This is an example where double line breaks \n\n are significant and cause a compilation error.

alpaca:compile({text, [<<"module a \n\nf a = let add x = x + x in\n\nadd a a\n\n">>]}).

Union type as ADT argument

The following test case fails:

union_type_as_adt_arg_test() ->
    Code = "module adt\n\n"
           "type union = int | atom\n\n"
           "type t = Union union\n\n"
           "make () = Union 1",
    ?assertMatch({ok, _}, module_typ_and_parse(Code)).

Error:

{error, {cannot_unify,adt,7,  {adt,"union",[],[]}, t_int}}

It seems like the knowledge about what members are in the union got lost along the way.

This is the line reporting the error: https://github.com/j14159/mlfe/blob/master/src/mlfe_typer.erl#L557

error, exit, throw and the typing of them

Need these three for lots of things, not least of which is being able to write stuff like basic test matchers without resorting to the FFI, something like

  • raise_error <term>
  • raise_exit <term> (only erlang:exit/1 for now)
  • raise_throw <term>

Maybe it makes sense to introduce special terms instead along the lines of error, exit, and throw, just not sure if those should be reserved words or not.

I'm proposing that these three are parametric error types potentially of the form t_err 'kind 'awhere 'kind is one of error, exit, or throw. 'a would be the type of the term used in the various user defined occurrences of t_err. E.g. raise_error :bad_arith would type to {t_err, error, t_atom}.

Unification:

  • with other t_err terms uses unification as normal. Unifying {t_err, throw, t_atom} with {t_err, error, t_atom} is a type error as would be {t_err, error, t_atom} and {t_err, error, t_string} without a type in scope that unifies them.
  • with non-t_err terms the errors unify to the other type. E.g. unifying t_int with {t_err, throw, t_string} yields t_int for both types. I think this leaves open the option later to parameterize every type with the potentially raised errors below, somewhat like receivers.

The latter of these allows the following to type to 'a -> 'a -> t_bool:

assert_equal a b = match (a == b) with
    true  -> true
  | false -> raise_throw (not_equal, a, b)

mlfe:file/{1,2}

Mirroring compile:file{1,2}, there ought to be mlfe:file/{1,2}. I intend to add this as soon as time permits.

Top level values (or nullary functions) are not usable at present

Currently, it is possible to define, but not use, zero-argument (nullary) functions. For example, the following throws a compilation error:

module example

x = 10

run () = x + x

Specifically:

{cannot_unify,main,5,t_int,{t_arrow,[],<0.178.0>}}

In other words, it's failing the type check because x is compiled as a zero-arg function, whereas the function + expects only integers. However, this compiles:

module example2

x = 10

run () = x

When called from Erlang, run () returns the zero arg function x, which can be invoked from Erlang to produce the value 10, but as far as I can tell but there is no way of getting the return value of x within Alpaca.

@j14159 has stated, and I agree, that nullary functions are not desirable and that it would be better if x is understood in this circumstance as a constant value. We discussed this on IRC and raised several issues:

  1. When should the value be calculated? Compile time? Runtime (i.e. on module load?)
  2. Where should the calculated values be stored? @j14159 suggested ETS, with the drawback that it would potentially be modifiable outside of the module
  3. Would we allow side effects in values? OCaml, for example, allows this:
let _ =
  print_string "Hello\n";;

edoc spec extraction

We might want to look into how edoc extracts specs of functions (which it then includes in the generated documentation).

Function gets constrained by callers

Consider this example:

module list_tests

is_empty l =
  match l with
    [] -> true
  | _ :: _ -> false

a () = is_empty []

b () = is_empty [:ok]

c () = is_empty [1]

This fails unexpectedly with {error,{cannot_unify,list_tests,12,t_atom,t_int}}.

The fact that we applied an atom to is_empty, should not specialize the function to only act on atoms.

Updates to records

Need a way to update or add fields to records, e.g.

let r = {x=1, y=2} in
  {r | z=3}

Support infix functions

It would be great if we could define infix functions, such as the usual suspects (>>=), <*> etc., and even |> can be implemented this way as a function (such as it is in Ocaml and Elm) instead of hardcoding it in the Elixir manner.

I had a bit of a go at hacking on the parser to allow for infix definitions and had some partial success: master...lepoetemaudit:master but it fell over when parsing any usage of the symbols as a function (it expects 'symbols' which do not include the operators, and this approach was a really inelegant hack to begin with).

I went and looked at the Elm source, which as far as I can tell distinguishes between normal symbols and operator only symbols and realised that to implement this properly we'd probably need to do the same, i.e. remove all the hardcoded infix operators in the lexing phase (and the binary << and >> delimiters are problematic too - perhaps python's b"" style of binary string quoting could be used instead?). Then we could have 'operator' strings defined in the lexer which can go into the environment map as functions, and are recognisable at definition and in usage as infix. It would be fairly easy to either provide the existing math ones as 'built-ins' that wrap the Erlang ones, or switch them out directly as of now in the parser.

I'm willing to give this a go if it is of interest. I'm in awe of the potential of having an ML on the Erlang VM.

Rename call_erlang keyword

Hello!

This keyword seems oddly named as it seems one would use it to call any BEAM language, not just Erlang. Seems odd to use this name to call Elixir or LFE.

Alternatives:

  • call
  • call_native
  • call_unsafe
  • call_beam

Sequences of bindings for `let ... in`

Rather than:

let add x y = x + y in
let square x = x * x in
add 2 (square 3)

I'd like to be able to do something like

let
  add x y = x + y;
  square x = x * x
in add 2 (square 3)

Exception AST nodes don't get their variables renamed

As part of AST rewriting before typing occurs the Alpaca compiler renames each variable in order to ensure uniqueness and that nothing escapes from things like receives. Arguments to throw, error, and exit aren't being correctly renamed at the moment.

Guards in function heads

Trying to compile @j14159's example code from another issue:

module guards
type make_it_work = int | string
let f x, is_int x = x + 1
let f x, is_string s = string_append "hello, x"

we get {error,{3,alpaca_parser,["syntax error before: ","','"]}}

For consistency with match expresssions, guards should be allowed in function heads (just like in Erlang).

Side note: Personally, I would rather drop guards completely from match expressions, since guards disable exhaustiveness checks ("Having a compiler warn about non-exaustive guards would be impossible in the general case, as it would involve solving the halting problem" (http://stackoverflow.com/a/7109455/347687)), and rather have an if expression.

OTP versions supported officialy?

I've seen in the README.md that there is only “official” support for 18.x because thats what you use @j14159. Personally I have to handle multiple versions on my system and swap back and forth all the time, and as such like to try to maintain compatibility in a given range. I already have an internal project which I have to keep compatible from 16B3 to current, but I do hope beeing able to drop 16 before years end.

Also I am trying to set up travis on my fork and wanted to know which versions of erlang I shall test against.

The current version does compile against the latest minor release of 17, 18 and 19, where 17 fails during compilation phase and 18 and 19 both pass all tests, but fail in dialyzer phase. For 17 there weren't even a dialyzer run, because of known trouble with modules generated from xrl and yrl files before OTP 18.

I can reproduce the exact same behaviour on my local system.

From what I have observed on my system and at travis, I'd guess supported range for now should be 18 and 19, while the tests should be run on 18.2, 18.3 and 19.0 at least, dropping 18.2 as soon 19.1 has been released. But I will follow what ever you suggest here.

I will do a WIP-PR in a couple of minutes.

Patterns in function definitions

Given:

type option 'a  = None | Some 'a

Instead of

map opt f = match opt with
    None -> opt
  | Some x -> Some (f x)

I want to be able to write

map None _ = None

map (Some x) f = Some (f x)

This will require reworking bits of the parser as terms can nest simple expressions (simple_expr) in parens making things like pattern matches legitimate terms. We'll need something of a distinction in order to make a list of patterns explicitly that and rule out the occurrence of a match or something equally nonsensical in a function declaration.

Polymorphic functions that pull record items aren't constraining the result type properly

The following three tests fail, we should expect that a get_x not returning an option won't unify with my_map/2's second argument and thus fail but the typer accepts the integer argument instead of rejecting it. I have not yet confirmed or denied that this behaviour is limited to records.

    , fun() ->
              Code =
                  "module fun_pattern_with_adt\n\n"
                  "type option 'a = None | Some 'a\n\n"
                  "my_map _ None = None\n\n"
                  "my_map f Some a = Some (f a)\n\n"
                  "doubler x = x * x\n\n"
                  "foo = my_map doubler 2",
              ?assertMatch(
                 {error, {cannot_unify, _, _, #adt{}, t_int}},
                 module_typ_and_parse(Code))
      end
    , fun() ->
              Code =
                  "module fun_pattern_with_adt\n\n"
                  "type option 'a = None | Some 'a\n\n"
                  "my_map _ None = None\n\n"
                  "my_map f Some a = Some (f a)\n\n"
                  "doubler x = x * x\n\n"
                  "get_x {x=x} = x\n\n"
                  "foo () = "
                  "  let rec = {x=1, y=2} in "
                  "  my_map doubler (get_x rec)",
              ?assertMatch(
                 {error, {cannot_unify, _, _, #adt{}, t_int}},
                 module_typ_and_parse(Code))
      end
    , fun() ->
              Code =
                  "module fun_pattern_with_adt\n\n"
                  "type option 'a = None | Some 'a\n\n"
                  "my_map _ None = None\n\n"
                  "my_map f Some a = Some (f a)\n\n"
                  "doubler x = x * x\n\n"
                  "get_x rec = match rec with {x=x} -> x\n\n"
                  "foo () = "
                  "  let rec = {x=1, y=2} in "
                  "  my_map doubler (get_x rec)",
              ?assertMatch(
                 {error, {cannot_unify, _, _, #adt{}, t_int}},
                 module_typ_and_parse(Code))
      end

Type aliasing

Consider the following example:

module shape

type radius = int

type shape = Circle radius

make_circle r = Circle r

test_circle () = make_circle 1

This unexpectedly fails to typecheck with the error:

exception error: no match of right hand side value 
                 {error,{cannot_unify,shape,9,{adt,"radius",[],[]},t_int}}

That is, radius is considered a distinct type from int.

The same module in OCaml compiles just fine:

type radius = int

type shape = Circle of radius

let make_circle r = Circle r

let test_circle () = make_circle 1

I think it makes sense for MLFE to behave the same way.

If I would like to make the radius type abstract (or opaque in dialyzer terms), I would hide it in a module (example in OCaml):

module Radius : sig
  type radius

  val make_radius: int -> radius
end =
struct
  type radius = int

  let make_radius i = i
end

type shape = Circle of Radius.radius

let make_circle r = Circle r

let test_circle () = make_circle (Radius.make_radius 1)

To achieve the same in MLFE, types should be module local by default, and you would have to export them via a export_type directive or similar. We would also need to be able to mark types as abstract. In Erlang/Dialyzer this is achieved by using the -opaque directive.

Automatic currying support

Taken from the discussion in issue #56 with @danabr and @lepoetemaudit

Single versions of a function will be automatically curried so the following will work:

foo x y = x + y

curry_foo () = 2 |> foo 1  -- results in 3

When there are different versions of the same named function (differing in arity), we will halt typing with an error in any ambiguous case. For example the following would generate an error along the lines of {error, {ambiguous_application, foo/1, foo/2}} - but maybe less hostile than that :)

foo x = x + x

foo x y = x + y

make_an_error () = 1 |> foo

While the following would not:

foo x = x + x

foo x y z = x + y + z

-- unit -> (int -> int)
passes_typing () = 2 |> foo 1

Feedback and differing opinions welcome. I think basic expression application as discussed in #56 has to be addressed before this issue is.

Automatic code formatter

One of the things I'm really enjoying about Elm is the official style guide and automatic code formatting tools. One can write code in any sloppy style, hit save, and then the formatter will rewrite the file in the correct style.

This allows us to avoid extra typing and removes all code style squabbling between developers! Hooray! It'd be great to have this for Alpaca.

I believe this is generally done by converting source code into AST, and then pretty-printing that back. We could leverage the compiler for the parsing/AST generation if there is a public function to do this in the compiler source code.

Cheers,
Louis

Compiler doesn't generate module_info/0,1 functions

This is easily spotted when trying to TAB-complete functions available in an mlfe module:

4> M1.
{compiled_module,basic_adt,"basic_adt.beam",
                 <<70,79,82,49,0,0,1,176,66,69,65,77,65,116,111,109,0,0,0,
                   36,0,0,0,6,9,...>>}
5> code:load_binary(element(2, M1), element(3, M1), element(4, M1)).
{module,basic_adt}
6> basic_adt:<tried hitting TAB here>*** ERROR: Shell process terminated! ***

=ERROR REPORT==== 1-Jul-2016::11:41:45 ===
Error in process <0.62.0> with exit value:
{undef,[{basic_adt,module_info,[],[]},
        {edlin_expand,expand_function_name,2,
                      [{file,"edlin_expand.erl"},{line,54}]},
        {group,get_line1,4,[{file,"src/4.1.1/group.erl"},{line,568}]},
        {group,get_chars_loop,8,[{file,"src/4.1.1/group.erl"},{line,462}]},
        {group,io_request,5,[{file,"src/4.1.1/group.erl"},{line,181}]},
        {group,server_loop,3,[{file,"src/4.1.1/group.erl"},{line,117}]}]}
Eshell V7.2  (abort with ^G)
1>

M:module_info/1,2 are actually thin shims which call erlang:get_module_info/1,2 which both already work with mlfe modules. I can provide a PR implementing generation of the shims - what do you think?

Individual record field access

At the moment we can only access members of records via a pattern match but we need to be able to do

let r = {x=1, y=2} in
  r.x + 2

I think this will require rewriting the AST in or before the code generation stage to put a pattern match up front, e.g. in Erlang:

case r of
    #{'__struct__' := record, 'x' := R_x} ->  R_x + 2

AST changes for function and type bindings

@danabr raised a point about type definitions vs types themselves in PR #116 , e.g.

type opt 'a = Some 'a | None
type int_opt = opt int

The name in the left-hand side of each of these could be viewed in a similar manner as function names in let bindings, that is, independent of their variables and members/bodies whereas opt int is a concrete type. Similarly as raised by @ypaq and others elsewhere:

let f x = x + x

could be viewed as syntactic sugar for

let f = fun x -> x + x

or something to that effect.

We might then add an alpaca_type_def node that binds variables and members to a type name while a member of a type binding stays as an alpaca_type AST node since it is in fact a concrete type. Functions then might be decomposed into two things as well:

  • an alpaca_fun node that lists function versions, each of which has their associated variables and bodies.
  • alpaca_fun_def that binds a name and arity (the latter for convenience) to a single alpaca_fun AST node.

This should make lambdas fairly simple to implement and makes types, functions, and values all operate in a similar manner.

Thoughts?

Specifying concrete types as type parameters instead of vars fails typing

This fails, the typer tries to unify t_int and undefined:

module n
type opt 'a = Some 'a | None
type u = U opt int
let f () = U Some 1

This also fails with the same basic unification error:

module m
export_type t
type t 'a = T 'a

module n
type u 'a = U m.t 'a
let f () = U m.T 1

I haven't dug in too deeply yet but I expect what's happening is that when we do:

type option 'a = Some 'a | None
type something_else = option int

The parser has no idea that int is supposed to be assigned to a variable 'a (or any variable, for that matter) and so trying to get that variable from the vars proplist in an #adt{} yields undefined.

I think the fix might be pretty simple: when we look through the parameters given for a type that's a member of another type, we just manufacture a new type variable for each type expression that isn't already a type var.

Type expression parsed differently whether a builtin type is used or not.

This code parses correctly:

type my_map = map atom atom

This one fails with ["syntax error before: ",[]]:

type my_atom = atom
type my_map = map my_atom atom

This again parses fine:

type my_atom = atom
type my_map = map (my_atom) atom

i.e, type my_map = map my_atom atom is parsed as type my_map = map (my_atom atom) .

Type checker does not check polymorphic user defined types

The following module should not pass type checking, but it does:

module tree

export height/1, fail/1

type tree 'a = Leaf | Node (tree, 'a, tree)

height t = 
  match t with
    Leaf -> 0
  | Node (l, _, r) -> 1 + (max (height l) (height r)) 

max a b = 
  match (a > b) with
    true -> a
  | false -> b

fail () = height 1

Function types in ADTs

We currently have no way to describe a function as a member of an ADT. Something pretty simple to start I think, along the lines of most ML-like things I've seen e.g. type add = int -> int -> int

Consider prefixing compiled Alpaca modules

When Elixir compiles modules, it prefixes the generated module name with 'Elixir.' (more info here). This means that any modules compiled with Elixir don't risk clashing with Erlang ones, and it keeps the Elixir standard library nicely namespaced. From within Elixir code, you don't need to use the prefix.

I believe the same would be useful in Alpaca - perhaps automatically prefix every generated module with alpaca_. It means a bit of extra typing when calling from the Erlang side, but it also means we could give nice names to Alpaca standard library modules like string instead of having to import e.g. alpaca_string within Alpaca code.

scanner.erl

I think scanner.erl should be renamed because it's very likely to clash with an already existing scanner.beam. It looks internal, so how about renaming to mlfe_scanner. Alternatively, and as a way to denote internal modules, maybe we should use mlfe_prv_scanner and probably rename other internal modules as well.

Thoughts?

Community?

I wonder if you plan to make a mailinglist/irc channel or some such to talk about the language. I and a few other people are very interested in discussing features and the like, but issues seem to be the wrong place to do it.

FFI Bridge Proposal

Bridges are to MLFE as ports are to Elm, without the send/receive and subscription semantics.

This is motivated by questions from @imetallica, discussion and feedback from @omarkj, and naming concerns from @lpil.

Example:

bridge append_ints = :erlang :"++" [list int, list int] list_int

Given the above in a module, the compiler will synthesize the function append_ints, typed to take to integer lists and return one that is a combination of both:

{t_arrow, [{t_list, t_int}, {t_list, t_int}], {t_list, t_int}}

The typer will trust that the author has considered the types involved and will expose this function for type checking. The code generator will create this function in the output Core Erlang AST and programmatically create the necessary checks for the return value. If we follow what Elm has done, this will create some substantial overhead on any recursive type like lists, maps, and recursive ADTs as each element must be checked before returning the result to MLFE code. A more problematic example:

type maybe_io_device = Ok pid unit | Error atom

bridge open_file = :file :open [string, list atom] io_device

If you refer to the erldocs for file:open/2, you'll notice the types I've given above to the bridge are incomplete, for example I'm not accounting for the fd() type which in the given docs doesn't appear to devolve to a pid. A larger issue is that currently the compiler would render the maybe_io_device ADT as either {'Ok', Pid} or {'Error', ErrorAtom} in any pattern match checking the validity of the return. This is relatively trivial to change and may make sense for simpler handling of common Erlang patterns directly as ADTs with no intermediary translation layer at all.

More specifically, given the changes to how ADTs are rendered, the code above would be synthesized to the following in the code generator:

open_file(Filename, Modes) ->
    case file:open(Filename, Modes) of
        {ok, IO}=Ok when is_pid(IO) -> Ok;
        {error, Reason}=Err when is_atom(Reason) -> Err
    end.

This has rather large safety implications:

  1. do we let this explode on the Erlang side unchecked, somewhat similarly to Elm?
  2. do we generate code that is already wrapped in a try/catch to account for errors, utilizing some sort of built-in type like type try 'x = Success 'x | Error erlang_exception? I use this type in Scala but does this remove Erlang-ness from the language?
  3. should we have a default safe mode as in the previous point and a keyword to remove the try/catch, e.g. unsafe bridge open_file = ... that doesn't wrap the result?
  4. should bridges always be unsafe but any that occur without a surrounding try/catch raise a compiler warning?

I'm leaning towards point 3 at the moment but curious about other opinions and would like to know if I've missed anything (beyond the complexity checking recursive structures entails).

Programming to an interface/signature and default implementations in modules

This issue is for a discussion and collection of ideas at least related to modules and signatures.

In issue #87 the distinction between OCaml's open and include came up and how the former exposes the imported module's functions in the module doing the opening. Copied from that issue:

My basic opinion right now: I see value in default implementations of signatures/interfaces but would like to consider more specificity than open appears to provide. Thoughts? Links to papers most definitely appreciated :D

rebar3 plugin

I'm excited to play around and hopefully help with mlfe. To start I began the process of creating a rebar3 plugin :) https://github.com/tsloughter/rebar_prv_mlfe

On a side note, I'd suggest either adding rebar.lock to the .gitignore of mlfe or just committing it even though it is empty. Hmm, maybe rebar3 should stop outputting it if it is empty though... I'll think about that :)

Anyway, just letting you know, so feel free to close this issue after reading it.

VIm syntax highlighting

Hello! This would be ace for us vim users.

You're probably too busy (and an emacs user) to work on this, but I thought I'd note this down for the future :)

Reuse variable in pattern matches for equality

In Erlang we can do the following to express two tuple items that are equal:

case Tuple of
    {X, X} -> ...
end

In Alpaca we currently have to do this:

match tuple with
  (x, y), x == y -> ...

I'd much prefer for us to be able to be like Erlang here:

match tuple with
  (x, x) -> ...

The latter seems like a more expressive form to me. This should be relatively simple to solve in the AST generation stage by rewriting multiple occurrences of a symbol in a pattern to a sequence of synthesized names and some added equality guards.

User defined types should be parameterizable with builtin types

Consider this example:

module user_defined_types

type proplist 'k 'v = list ('k, 'v)

type optlist 'v = proplist atom 'v

This fails with:

{error, {badmatch, {error, {6,mlfe_parser, ["syntax error before: ",["\"atom\""]]}}}

The parser expects a user defined type to only take type variables. See poly_type in mlfe_paser.yrl.

mlfe_typer also has this assumption.

Can't refer to a type qualified with a module name

Given

module m
type t = int

The following yields a syntax error but should be allowed:

module n
type u = m.t

This is especially important since the changes discussed in #62 won't permit m.t to be imported but should still allow it to be referenced.

Improve type and function sharing between modules

Short list, driven by discussion on PR #61 with @danabr and from @lepoetemaudit's infix function work:

  • all type names available by default but not implementations (e.g. type constructors). Exporting a type exposes its implementation. This is to let us use a module's type in other modules' types without needing the details (information hiding, abstract/opaque types).
  • individual functions used in other modules without qualifying them with their module name, this will make infix operators more useful.

Per @danabr we should consider a single import directive that allows importing specific functions, types, or even a subset of a type's constructors.

Ideas/expansions/criticisms most welcome.

Suggestions for a name

Let's collect some suggestions for a name.

Peter Landin -> lander
Robin Milner -> ermil
Tarm (birthplace of Agner Krarup Erlang) plus ML -> tarmel or tarml
ML + erl -> merl, but merl already is a known Erlang module
lambda calculus -> lama

Idea: skip process handling

Hi, cool project.

Just an idea: Why not skip support for message send/receive?
Mostly you use the gen_server.erl anyway and if you really want to
send/receive you encapsulate that in your own Erlang module anyway.

More docs on calling Alpaca from Erlang

Specific items:

  • modules Alpaca generates are prefixed with alpaca_
  • to pattern match correctly, records and maps passed to Alpaca need a particular __struct__ field to exist.

Suggestion: Optional arity for exports

It often is frustrating to update arity for changed functions' signatures. While there are definitely cases when one would keep some functions out from being exported, often times the need to maintain arity in export becomes a nuisance.

My proposal is to make arity specification in export optional, and if it isn't specified, export all functions with a given name, regardless of arity.

Unification failure when subexpression is put in argument position

Consider this function:

duplicate count el =
  match count with
    0 -> []
  | _ -> el :: (duplicate (count-1) el) 

Compiling it crashes with:

exception error: no match of right hand side value 
                 {error,{cannot_unify,example,6,
                                      {t_arrow,[<0.90.0>],<0.91.0>},
                                      t_int}}
  in function  mlfe:compile/2 ([...]/mlfe/src/mlfe.erl, line 55)

However, this type checks and compiles just fine:

duplicate count el =
  match count with
    0 -> []
  | _ ->
    let next_count = count - 1 in
    el :: (duplicate next_count el)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.