Skip to content

Commit

Permalink
Merge pull request #84 from j14159/import-funs
Browse files Browse the repository at this point in the history
Export all arities for a function, foundations of function imports
  • Loading branch information
j14159 authored Jan 11, 2017
2 parents ab1cdb8 + c9e9303 commit 045e92c
Show file tree
Hide file tree
Showing 16 changed files with 1,118 additions and 448 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Here's an example module:
module simple_example

-- a basic top-level function:
add2 x = x + 2
let add2 x = x + 2

something_with_let_bindings x =
let something_with_let_bindings x =
-- a function:
let adder a b = a + b in
-- a variable (immutable):
Expand All @@ -74,13 +74,13 @@ Here's an example module:
messages, that increments its state by received integers
and can be queried for its state.
-}
will_be_a_process x = receive with
let will_be_a_process x = receive with
i -> will_be_a_process (x + i)
| Fetch sender ->
let sent = send x sender in
will_be_a_process x

start_a_process init = spawn will_be_a_process init
let start_a_process init = spawn will_be_a_process init

# Licensing
Alpaca is released under the terms of the Apache License, Version 2.0
Expand Down Expand Up @@ -161,7 +161,7 @@ For a simple module, open `src/example.alp` and add the following:

export add/2

add x y = x + y
let add x y = x + y

The above is just what it looks like: a module named `example` with a
function that adds two integers. You can call the function directly
Expand Down Expand Up @@ -263,7 +263,7 @@ See `src/builtin_types.hrl` for the included functions.
## Pattern Matching
Pretty simple and straightforward for now:

length l = match l with
let length l = match l with
[] -> 0
| h :: t -> 1 + (length t)

Expand Down Expand Up @@ -312,7 +312,7 @@ a simple get-by-key function as follows:

type my_opt 'a = Some 'a | None

get_by_key m k =
let get_by_key m k =
match m with
#{k => v} -> Some v
| _ -> None
Expand All @@ -339,7 +339,7 @@ An example:
type maybe_success 'error 'ok = Error 'error | Success 'ok

-- Apply a function to a successful result or preserve an error.
map e f = match e with
let try_map e f = match e with
Error _ -> e
| Success ok -> Success (f ok)

Expand All @@ -349,7 +349,7 @@ there aren't even proper assertions available. If the compiler is
invoked with options `[test]`, the following will synthesize and
export a function called `add_2_and_2_test`:

add x y = x + y
let add x y = x + y

test "add 2 and 2" =
let res = add 2 2 in
Expand All @@ -370,13 +370,13 @@ checked. Type errors in a test will always cause a compilation error.
## Processes
An example:

f x = receive with
let f x = receive with
(y, sender) ->
let z = x + y in
let sent = send z sender in
f z

start_f init = spawn f init
let start_f init = spawn f init

All of the above is type checked, including the spawn and message sends.
Any expression that contains a `receive` block becomes a "receiver"
Expand All @@ -403,10 +403,10 @@ support spawning functions in other modules fairly soon.

Note that the following will yield a type error:

a x = receive with
let a x = receive with
i -> b x + i

b x = receive with
let b x = receive with
f -> a x +. i

This is because `b` is a `t_float` receiver while `a` is a `t_int`
Expand Down Expand Up @@ -516,9 +516,9 @@ arrow type and type schema instantiation are concerned.

export add/2

add x y = adder x y
let add x y = adder x y

adder x y = x + y
let adder x y = x + y

The forward reference in `add/2` is permitted but currently leads to some wasted
work. When typing `add/2` the typer encounters a reference to `adder/2` that is
Expand Down
94 changes: 65 additions & 29 deletions Tour.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<li><a href="#sec-5">5. User Defined Types: ADTs</a></li>
<li><a href="#sec-6">6. Tests</a></li>
<li><a href="#sec-7">7. Processes</a></li>
<li><a href="#sec-8">8. Exporting and Importing Functions</a></li>
</ul>
</div>
</div>
Expand Down Expand Up @@ -100,7 +101,7 @@ Here's a simple example of a module:
{- Our double function defines an "add" function inside of itself.
Comments for now are C-style.
-}
double x =
let double x =
let add a b = a + b in
add x x

Expand All @@ -110,7 +111,7 @@ Here's a simple example of a module:
type even_or_odd = Even int | Odd int

-- A function from integers to our ADT:
even_or_odd x =
let even_or_odd x =
let rem = x % 2 in
match rem with
0 -> Even x
Expand Down Expand Up @@ -175,11 +176,11 @@ These types are all "parametrically polymorphic", or "generics" for those of us

Tuples, like functions, have a specific arity (number of contained elements). In Alpaca the typing of tuples covers both their arity **and** the type of each element. Let's introduce pattern matching here to illustrate their structure:

third my_tuple =
let third my_tuple =
match my_tuple with
(_, _, x) -> x

third_string my_tuple =
let third_string my_tuple =
match my_tuple with
(_, _, x), is_string x -> x

Expand All @@ -198,9 +199,9 @@ Tuples, like functions, have a specific arity (number of contained elements). I
third (1, 2, 3, 4)

{- This function will also fail to compile because tuples of arity 2
* those of arity 3 are fundamentally different types:
and those of arity 3 are fundamentally different types:
-}
second_or_third my_tuple =
let second_or_third my_tuple =
match my_tuple with
(_, _, x) -> x
| (_, x) -> x
Expand All @@ -221,12 +222,12 @@ We can build lists up with the cons operator `::` or as literals:

Let's revisit pattern matching here as well with both forms:

length my_list =
let length my_list =
match my_list with
[] -> 0
| _ :: t -> 1 + (length t)
is_length_3 my_list =
let is_length_3 my_list =
match my_list with
[_, _, _] -> true
| _ -> false
Expand Down Expand Up @@ -257,7 +258,7 @@ Records can be created ad-hoc wherever you like as in OCaml and Elm and you can
information about the hello field. The return type of calling the
function below with that record will be (int, {x: int, hello: string}).
-}
x_in_a_tuple my_rec =
let x_in_a_tuple my_rec =
match my_rec with
{x=xx} -> (xx, my_rec)

Expand Down Expand Up @@ -288,7 +289,7 @@ Records can be created ad-hoc wherever you like as in OCaml and Elm and you can

The return of `identity(new HasXY(1, "world"))` "loses" the information that the passed-in argument has a `hello` member of type `String`.

identity my_rec =
let identity my_rec =
match my_rec with
{x=_} -> my_rec

Expand All @@ -314,7 +315,7 @@ Process identifiers (references to processes to which we can send messages) are

Inside of a function we can define both immutable variables and new functions:

f x =
let f x =
let double y = y + y in -- this is a single argument function
let doubled_x = double x in -- a variable named "double_x"
doubled_x + x -- the expression returned as a result
Expand All @@ -327,13 +328,13 @@ As Alpaca is an expression-oriented language, there are no return statements. J
than a float (e.g. an integer or string), the compiler would fail with
a type error.
-}
double x = x *. 2.0
let double x = x *. 2.0

Explicit type specifications for variables and functions is a planned feature for version 0.3.0.

While functions with no arguments aren't supported ("nullary" or arity of zero) we can use the unit term `()` if we don't need or want to pass anything specific. Let's introduce the basic foreign-function interface here to call an Erlang printing method:

print_hello () =
let print_hello () =
beam :io :format ["Hello~n", []] with _ -> ()

## The Foreign Function Interface<a id="sec-4-1" name="sec-4-1"></a>
Expand Down Expand Up @@ -378,14 +379,14 @@ Some other simple type checking functions are also usable in pattern match guard

A word of caution: strings are encoded as binaries, and chars as lists so if we call the following example `f/1` with a string, we will **always** get a binary back (assuming there's an ADT covering both):

f x =
let f x =
match x with
b, is_binary b -> b
| s, is_string s -> s

And here we will always get a list instead of a character list (same ADT restriction):

g x =
let g x =
match x with
l, is_list l -> l
| c, is_chars c -> c
Expand All @@ -411,7 +412,7 @@ We can also use "type constructors" and type variables to be a bit more expressi
instead give it a `map atom (list string)` and an atom for the key,
the return type will be `opt (list string)`.
-}
map_get key the_map =
let map_get key the_map =
match the_map with
#{key => value} -> Some value
| _ -> None
Expand All @@ -424,7 +425,7 @@ We can use the basic Alpaca types as well, here's a type that describes parsed J

If the above type is in scope (in the module, or imported), the following function's type will be inferred as one from `json` to `atom`:

f x =
let f x =
match x with
i, is_integer i -> :integer
| f, is_float f -> :float
Expand All @@ -437,7 +438,7 @@ If the inferencer has more than one ADT unifying integers and floats in scope, i
| list json
| list (string, json)

f x =
let f x =
match x with
i, is_integer i -> :integer
| f, is_float f -> :float
Expand All @@ -455,7 +456,7 @@ Tests:

Here's a simple example:

add x y = x + y
let add x y = x + y

test "add 2 2 should result in 4" =
add 2 2
Expand All @@ -466,23 +467,23 @@ While the above test is type checked and will happily be compiled, we lack asser

export add/2

add x y = x + y
let add x y = x + y

test "add 2 2 should result in 4" = test_equal (add 2 2) 4

{- Test the equality of two terms, throwing an exception if they're
not equal. The two terms will need to be the same type for any
call to this to succeed:
-}
test_equal x y =
let test_equal x y =
match (x == y) with
true -> :passed
| false ->
let msg = format_msg "Not equal: ~w and ~w" x y in
beam :erlang :error [msg] with _ -> :failed

-- formats a failure message:
format_msg base x y =
let format_msg base x y =
let m = beam :io_lib :format [base, [x, y]] with msg -> msg in
beam :lists :flatten [m] with msg, is_chars msg -> msg

Expand All @@ -498,15 +499,15 @@ Process support in Alpaca is still pretty basic but the following are all suppor

A basic example will probably help:

a_counting_function x =
let a_counting_function x =
receive with
"add" -> a_counting_function x + 1
| "sub" -> a_counting_function x - 1

{- If a_counting_function/1 is exported from the module, the following
* will spawn a `pid string`, that is, a "process that can receive
* strings". Note that this is not a valid top-level entry for a module,
* we just want a few simple examples.
will spawn a `pid string`, that is, a "process that can receive
strings". Note that this is not a valid top-level entry for a module,
we just want a few simple examples.
-}
let my_pid = spawn a_counting_function 0

Expand All @@ -518,20 +519,20 @@ A basic example will probably help:

The type inferencer looks at the entire call graph of the function being spawned to determine type of messages that the process is capable of receiving. Any expression that contains a call to `receive` becomes a "receiver" that carries the type of messages handled so if we have something like `let x = receive with i, is_integer i -> i`, that entire expression is a receiver. If a function contains it like this:

f x =
let f x =
let x = receive with i, is_integer i -> i in
i

then the entire function is considered a receiver too.

Mutually recursive functions can be spawned as well provided that **if** they're both receivers, the message receive types match:

a () =
let a () =
receive with
:b -> b ()
| _ -> a ()

b () =
let b () =
receive with
"a" -> a ()
| _ -> b ()
Expand All @@ -540,3 +541,38 @@ Mutually recursive functions can be spawned as well provided that **if** they're
type a_and_b = string | atom

As an aside, both the functions `a/1` and `b/1` above have the return type `rec`, meaning "infinitely recursive" since neither ever return a value. This is a legitimate type in Alpaca.

# Exporting and Importing Functions<a id="sec-8" name="sec-8"></a>

There are a few handy shortcuts for exporting and importing functions to be aware of. If you have different versions of a function (same name, different number of arguments) and you want to export them all, you can leave out the arity when exporting, e.g.

module example

-- this will export foo/1 and foo/2 for you:
export foo

let foo x = x

let foo x y = x + y

You can also import functions with or without arity, e.g. `import example.foo/1` for only the first `foo` in the example above or `import example.foo` for both versions. Subsets of a module's functions can be imported in a list format as well, for example if we have a simple math helper module:

-- some simple math functions:
module math

export add, sub, mult

let add x y = x + y
let sub x y = x - y
let mult x y = x * y

We can then import two of the functions with a list:

-- imports and uses two of the math functions
module example

import math.[add, sub]

let f () = add 2 (sub 5 3)

When giving lists of functions to import you can include arity if you only want a specific version of a function.
Loading

0 comments on commit 045e92c

Please sign in to comment.