mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-11-25 20:14:30 +03:00
608 lines
19 KiB
Markdown
608 lines
19 KiB
Markdown
---
|
|
language: ATS
|
|
contributors:
|
|
- ["Mark Barbone", "https://github.com/mb64"]
|
|
filename: learnats.dats
|
|
---
|
|
|
|
ATS is a low-level functional programming language. It has a powerful type
|
|
system which lets you write programs with the same level of control and
|
|
efficiency as C, but in a memory safe and type safe way.
|
|
|
|
The ATS type system supports:
|
|
|
|
* Full type erasure: ATS compiles to efficient C
|
|
* Dependent types, including [LF](http://twelf.org/wiki/LF) and proving
|
|
metatheorems
|
|
* Refinement types
|
|
* Linearity for resource tracking
|
|
* An effect system that tracks exceptions, mutation, termination, and other
|
|
side effects
|
|
|
|
This tutorial is not an introduction to functional programming, dependent types,
|
|
or linear types, but rather to how they all fit together in ATS. That said, ATS
|
|
is a very complex language, and this tutorial doesn't cover it all. Not only
|
|
does ATS's type system boast a wide array of confusing features, its
|
|
idiosyncratic syntax can make even "simple" examples hard to understand. In the
|
|
interest of keeping it a reasonable length, this document is meant to give a
|
|
taste of ATS, giving a high-level overview of what's possible and how, rather
|
|
than attempting to fully explain how everything works.
|
|
|
|
You can [try ATS in your browser](http://www.ats-lang.org/SERVER/MYCODE/Patsoptaas_serve.php),
|
|
or install it from [http://www.ats-lang.org/](http://www.ats-lang.org/).
|
|
|
|
|
|
```ocaml
|
|
// Include the standard library
|
|
#include "share/atspre_define.hats"
|
|
#include "share/atspre_staload.hats"
|
|
|
|
// To compile, either use
|
|
// $ patscc -DATS_MEMALLOC_LIBC program.dats -o program
|
|
// or install the ats-acc wrapper https://github.com/sparverius/ats-acc and use
|
|
// $ acc pc program.dats
|
|
|
|
// C-style line comments
|
|
/* and C-style block comments */
|
|
(* as well as ML-style block comments *)
|
|
|
|
/*************** Part 1: the ML fragment ****************/
|
|
|
|
val () = print "Hello, World!\n"
|
|
|
|
// No currying
|
|
fn add (x: int, y: int) = x + y
|
|
|
|
// fn vs fun is like the difference between let and let rec in OCaml/F#
|
|
fun fact (n: int): int = if n = 0 then 1 else n * fact (n-1)
|
|
|
|
// Multi-argument functions need parentheses when you call them; single-argument
|
|
// functions can omit parentheses
|
|
val forty_three = add (fact 4, 19)
|
|
|
|
// let is like let in SML
|
|
fn sum_and_prod (x: int, y: int): (int, int) =
|
|
let
|
|
val sum = x + y
|
|
val prod = x * y
|
|
in (sum, prod) end
|
|
|
|
// 'type' is the type of all heap-allocated, non-linear types
|
|
// Polymorphic parameters go in {} after the function name
|
|
fn id {a:type} (x: a) = x
|
|
|
|
// ints aren't heap-allocated, so we can't pass them to 'id'
|
|
// val y: int = id 7 // doesn't compile
|
|
|
|
// 't@ype' is the type of all non-linear potentially unboxed types. It is a
|
|
// supertype of 'type'.
|
|
// Templated parameters go in {} before the function name
|
|
fn {a:t@ype} id2 (x: a) = x
|
|
|
|
val y: int = id2 7 // works
|
|
|
|
// can't have polymorphic t@ype parameters
|
|
// fn id3 {a:t@ype} (x: a) = x // doesn't compile
|
|
|
|
// explicity specifying type parameters:
|
|
fn id4 {a:type} (x: a) = id {a} x // {} for non-template parameters
|
|
fn id5 {a:type} (x: a) = id2<a> x // <> for template parameters
|
|
fn id6 {a:type} (x: a) = id {..} x // {..} to explicitly infer it
|
|
|
|
// Heap allocated shareable datatypes
|
|
// using datatypes leaks memory
|
|
datatype These (a:t@ype, b:t@ype) = This of a
|
|
| That of b
|
|
| These of (a, b)
|
|
|
|
// Pattern matching using 'case'
|
|
fn {a,b: t@ype} from_these (x: a, y: b, these: These(a,b)): (a, b) =
|
|
case these of
|
|
| This(x) => (x, y) // Shadowing of variable names is fine; here, x shadows
|
|
// the parameter x
|
|
| That(y) => (x, y)
|
|
| These(x, y) => (x, y)
|
|
|
|
// Partial pattern match using 'case-'
|
|
// Will throw an exception if passed This
|
|
fn {a,b:t@ype} unwrap_that (these: These(a,b)): b =
|
|
case- these of
|
|
| That(y) => y
|
|
| These(_, y) => y
|
|
|
|
|
|
/*************** Part 2: refinements ****************/
|
|
|
|
// Parameterize functions by what values they take and return
|
|
fn cool_add {n:int} {m:int} (x: int n, y: int m): int (n+m) = x + y
|
|
|
|
// list(a, n) is a list of n a's
|
|
fun square_all {n:int} (xs: list(int, n)): list(int, n) =
|
|
case xs of
|
|
| list_nil() => list_nil()
|
|
| list_cons(x, rest) => list_cons(x * x, square_all rest)
|
|
|
|
fn {a:t@ype} get_first {n:int | n >= 1} (xs: list(a, n)): a =
|
|
case+ xs of // '+' asks ATS to prove it's total
|
|
| list_cons(x, _) => x
|
|
|
|
// Can't run get_first on lists of length 0
|
|
// val x: int = get_first (list_nil()) // doesn't compile
|
|
|
|
// in the stdlib:
|
|
// sortdef nat = {n:int | n >= 0}
|
|
// sortdef pos = {n:int | n >= 1}
|
|
|
|
fn {a:t@ype} also_get_first {n:pos} (xs: list(a, n)): a =
|
|
let
|
|
val+ list_cons(x, _) = xs // val+ also works
|
|
in x end
|
|
|
|
// tail-recursive reverse
|
|
fun {a:t@ype} reverse {n:int} (xs: list(a, n)): list(a, n) =
|
|
let
|
|
// local functions can use type variables from their enclosing scope
|
|
// this one uses both 'a' and 'n'
|
|
fun rev_helper {i:nat} (xs: list(a, n-i), acc: list(a, i)): list(a, n) =
|
|
case xs of
|
|
| list_nil() => acc
|
|
| list_cons(x, rest) => rev_helper(rest, list_cons(x, acc))
|
|
in rev_helper(xs, list_nil) end
|
|
|
|
// ATS has three context-dependent namespaces
|
|
// the two 'int's mean different things in this example, as do the two 'n's
|
|
fn namespace_example {n:int} (n: int n): int n = n
|
|
// ^^^ sort namespace
|
|
// ^ ^^^ ^ ^^^ ^ statics namespace
|
|
// ^^^^^^^^^^^^^^^^^ ^ ^ value namespace
|
|
|
|
// a termination metric can go in .< >.
|
|
// it must decrease on each recursive call
|
|
// then ATS will prove it doesn't infinitely recurse
|
|
fun terminating_factorial {n:nat} .<n>. (n: int n): int =
|
|
if n = 0 then 1 else n * terminating_factorial (n-1)
|
|
|
|
|
|
/*************** Part 3: the LF fragment ****************/
|
|
|
|
// ATS supports proving theorems in LF (http://twelf.org/wiki/LF)
|
|
|
|
// Relations are represented by inductive types
|
|
|
|
// Proofs that the nth fibonacci number is f
|
|
dataprop Fib(n:int, m:int) =
|
|
| FibZero(0, 0)
|
|
| FibOne(1, 1)
|
|
| {n, f1, f2: int} FibInd(n, f1 + f2) of (Fib(n-1,f1), Fib(n-2,f2))
|
|
|
|
// Proved-correct fibonacci implementation
|
|
// [A] B is an existential type: "there exists A such that B"
|
|
// (proof | value)
|
|
fun fib {n:nat} .<n>. (n: int n): [f:int] (Fib(n,f) | int f) =
|
|
if n = 0 then (FibZero | 0) else
|
|
if n = 1 then (FibOne | 1) else
|
|
let
|
|
val (proof1 | val1) = fib (n-1)
|
|
val (proof2 | val2) = fib (n-2)
|
|
// the existential type is inferred
|
|
in (FibInd(proof1, proof2) | val1 + val2) end
|
|
|
|
// Faster proved-correct fibonacci implementation
|
|
fn fib_tail {n:nat} (n: int n): [f:int] (Fib(n,f) | int f) =
|
|
let
|
|
fun loop {i:int | i < n} {f1, f2: int} .<n - i>.
|
|
(p1: Fib(i,f1), p2: Fib(i+1,f2)
|
|
| i: int i, f1: int f1, f2: int f2, n: int n
|
|
): [f:int] (Fib(n,f) | int f) =
|
|
if i = n - 1
|
|
then (p2 | f2)
|
|
else loop (p2, FibInd(p2,p1) | i+1, f2, f1+f2, n)
|
|
in if n = 0 then (FibZero | 0) else loop (FibZero, FibOne | 0, 0, 1, n) end
|
|
|
|
// Proof-level lists of ints, of type 'sort'
|
|
datasort IntList = ILNil of ()
|
|
| ILCons of (int, IntList)
|
|
|
|
// ILAppend(x,y,z) iff x ++ y = z
|
|
dataprop ILAppend(IntList, IntList, IntList) =
|
|
| {y:IntList} AppendNil(ILNil, y, y)
|
|
| {a:int} {x,y,z: IntList}
|
|
AppendCons(ILCons(a,x), y, ILCons(a,z)) of ILAppend(x,y,z)
|
|
|
|
// prfuns/prfns are compile-time functions acting on proofs
|
|
|
|
// metatheorem: append is total
|
|
prfun append_total {x,y: IntList} .<x>. (): [z:IntList] ILAppend(x,y,z)
|
|
= scase x of // scase lets you inspect static arguments (only in prfuns)
|
|
| ILNil() => AppendNil
|
|
| ILCons(a,rest) => AppendCons(append_total())
|
|
|
|
|
|
/*************** Part 4: views ****************/
|
|
|
|
// views are like props, but linear; ie they must be consumed exactly once
|
|
// prop is a subtype of view
|
|
|
|
// 'type @ address' is the most basic view
|
|
|
|
fn {a:t@ype} read_ptr {l:addr} (pf: a@l | p: ptr l): (a@l | a) =
|
|
let
|
|
// !p searches for usable proofs that say something is at that address
|
|
val x = !p
|
|
in (pf | x) end
|
|
|
|
// oops, tried to dereference a potentially invalid pointer
|
|
// fn {a:t@ype} bad {l:addr} (p: ptr l): a = !p // doesn't compile
|
|
|
|
// oops, dropped the proof (leaked the memory)
|
|
// fn {a:t@ype} bad {l:addr} (pf: a@l | p: ptr l): a = !p // doesn't compile
|
|
|
|
fn inc_at_ptr {l:addr} (pf: int@l | p: ptr l): (int@l | void) =
|
|
let
|
|
// !p := value writes value to the location at p
|
|
// like !p, it implicitly searches for usable proofs that are in scope
|
|
val () = !p := !p + 1
|
|
in (pf | ()) end
|
|
|
|
// threading proofs through gets annoying
|
|
fn inc_three_times {l:addr} (pf: int@l | p: ptr l): (int@l | void) =
|
|
let
|
|
val (pf2 | ()) = inc_at_ptr (pf | p)
|
|
val (pf3 | ()) = inc_at_ptr (pf2 | p)
|
|
val (pf4 | ()) = inc_at_ptr (pf3 | p)
|
|
in (pf4 | ()) end
|
|
|
|
// so there's special syntactic sugar for when you don't consume a proof
|
|
fn dec_at_ptr {l:addr} (pf: !int@l | p: ptr l): void =
|
|
!p := !p - 1 // ^ note the exclamation point
|
|
|
|
fn dec_three_times {l:addr} (pf: !int@l | p: ptr l): void =
|
|
let
|
|
val () = dec_at_ptr (pf | p)
|
|
val () = dec_at_ptr (pf | p)
|
|
val () = dec_at_ptr (pf | p)
|
|
in () end
|
|
|
|
// dataview is like dataprop, but linear
|
|
// A proof that either the address is null, or there is a value there
|
|
dataview MaybeNull(a:t@ype, addr) =
|
|
| NullPtr(a, null)
|
|
| {l:addr | l > null} NonNullPtr(a, l) of (a @ l)
|
|
|
|
fn maybe_inc {l:addr} (pf: !MaybeNull(int, l) | p: ptr l): void =
|
|
if ptr1_is_null p
|
|
then ()
|
|
else let
|
|
// Deconstruct the proof to access the proof of a @ l
|
|
prval NonNullPtr(value_exists) = pf
|
|
val () = !p := !p + 1
|
|
// Reconstruct it again for the caller
|
|
prval () = pf := NonNullPtr(value_exists)
|
|
in () end
|
|
|
|
// array_v(a,l,n) represents and array of n a's at location l
|
|
// this gets compiled into an efficient for loop, since all proofs are erased
|
|
fn sum_array {l:addr}{n:nat} (pf: !array_v(int,l,n) | p: ptr l, n: int n): int =
|
|
let
|
|
fun loop {l:addr}{n:nat} .<n>. (
|
|
pf: !array_v(int,l,n)
|
|
| ptr: ptr l,
|
|
length: int n,
|
|
acc: int
|
|
): int = if length = 0
|
|
then acc
|
|
else let
|
|
prval (head, rest) = array_v_uncons(pf)
|
|
val result = loop(rest | ptr_add<int>(ptr, 1), length - 1, acc + !ptr)
|
|
prval () = pf := array_v_cons(head, rest)
|
|
in result end
|
|
in loop (pf | p, n, 0) end
|
|
|
|
// 'var' is used to create stack-allocated (lvalue) variables
|
|
val seven: int = let
|
|
var res: int = 3
|
|
// they can be modified
|
|
val () = res := res + 1
|
|
// addr@ res is a pointer to it, and view@ res is the associated proof
|
|
val (pf | ()) = inc_three_times(view@ res | addr@ res)
|
|
// need to give back the view before the variable goes out of scope
|
|
prval () = view@ res := pf
|
|
in res end
|
|
|
|
// References let you pass lvalues, like in C++
|
|
fn square (x: &int): void =
|
|
x := x * x // they can be modified
|
|
|
|
val sixteen: int = let
|
|
var res: int = 4
|
|
val () = square res
|
|
in res end
|
|
|
|
fn inc_at_ref (x: &int): void =
|
|
let
|
|
// like vars, references have views and addresses
|
|
val (pf | ()) = inc_at_ptr(view@ x | addr@ x)
|
|
prval () = view@ x := pf
|
|
in () end
|
|
|
|
// Like ! for views, & references are only legal as argument types
|
|
// fn bad (x: &int): &int = x // doesn't compile
|
|
|
|
// this takes a proof int n @ l, but returns a proof int (n+1) @ l
|
|
// since they're different types, we can't use !int @ l like before
|
|
fn refined_inc_at_ptr {n:int}{l:addr} (
|
|
pf: int n @ l | p: ptr l
|
|
): (int (n+1) @ l | void) =
|
|
let
|
|
val () = !p := !p + 1
|
|
in (pf | ()) end
|
|
|
|
// special syntactic sugar for returning a proof at a different type
|
|
fn refined_dec_at_ptr {n:int}{l:addr} (
|
|
pf: !int n @ l >> int (n-1) @ l | p: ptr l
|
|
): void =
|
|
!p := !p - 1
|
|
|
|
// legal but very bad code
|
|
prfn swap_proofs {v1,v2:view} (a: !v1 >> v2, b: !v2 >> v1): void =
|
|
let
|
|
prval tmp = a
|
|
prval () = a := b
|
|
prval () = b := tmp
|
|
in () end
|
|
|
|
// also works with references
|
|
fn refined_square {n:int} (x: &int n >> int (n*n)): void =
|
|
x := x * x
|
|
|
|
fn replace {a,b:vtype} (dest: &a >> b, src: b): a =
|
|
let
|
|
val old = dest
|
|
val () = dest := src
|
|
in old end
|
|
|
|
// values can be uninitialized
|
|
fn {a:vt@ype} write (place: &a? >> a, value: a): void =
|
|
place := value
|
|
|
|
val forty: int = let
|
|
var res: int
|
|
val () = write (res, 40)
|
|
in res end
|
|
|
|
// viewtype: a view and a type
|
|
viewtypedef MaybeNullPtr(a:t@ype) = [l:addr] (MaybeNull(a, l) | ptr l)
|
|
// MaybeNullPtr has type 'viewtype' (aka 'vtype')
|
|
// type is a subtype of vtype and t@ype is a subtype of vt@ype
|
|
|
|
// The most general identity function:
|
|
fn {a:vt@ype} id7 (x: a) = x
|
|
|
|
// since they contain views, viewtypes must be used linearly
|
|
// fn {a:vt@ype} duplicate (x: a) = (x, x) // doesn't compile
|
|
// fn {a:vt@ype} ignore (x: a) = () // doesn't compile
|
|
|
|
// arrayptr(a,l,n) is a convenient built-in viewtype
|
|
fn easier_sum_array {l:addr}{n:nat} (p: !arrayptr(int,l,n), n: int n): int =
|
|
let
|
|
fun loop {i:nat | i <= n} (
|
|
p: !arrayptr(int,l,n), n: int n, i: int i, acc: int
|
|
): int =
|
|
if i = n
|
|
then acc
|
|
else loop(p, n, i+1, acc + p[i])
|
|
in loop(p, n, 0, 0) end
|
|
|
|
|
|
/*************** Part 5: dataviewtypes ****************/
|
|
|
|
// a dataviewtype is a heap-allocated non-shared inductive type
|
|
|
|
// in the stdlib:
|
|
// dataviewtype list_vt(a:vt@ype, int) =
|
|
// | list_vt_nil(a, 0)
|
|
// | {n:nat} list_vt_cons(a, n+1) of (a, list_vt(a, n))
|
|
|
|
fn {a:vt@ype} length {n:int} (l: !list_vt(a, n)): int n =
|
|
let // ^ since we're not consuming it
|
|
fun loop {acc:int} (l: !list_vt(a, n-acc), acc: int acc): int n =
|
|
case l of
|
|
| list_vt_nil() => acc
|
|
| list_vt_cons(head, tail) => loop(tail, acc + 1)
|
|
in loop (l, 0) end
|
|
|
|
// vvvvv not vt@ype, because you can't easily get rid of a vt@ype
|
|
fun {a:t@ype} destroy_list {n:nat} (l: list_vt(a,n)): void =
|
|
case l of
|
|
// ~ pattern match consumes and frees that node
|
|
| ~list_vt_nil() => ()
|
|
| ~list_vt_cons(_, tail) => destroy_list tail
|
|
|
|
// unlike a datatype, a dataviewtype can be modified:
|
|
fun {a:vt@ype} push_back {n:nat} (
|
|
x: a,
|
|
l: &list_vt(a,n) >> list_vt(a,n+1)
|
|
): void =
|
|
case l of
|
|
| ~list_vt_nil() => l := list_vt_cons(x, list_vt_nil)
|
|
// @ pattern match disassembles/"unfolds" the datavtype's view, so you can
|
|
// modify its components
|
|
| @list_vt_cons(head, tail) => let
|
|
val () = push_back (x, tail)
|
|
// reassemble it with fold@
|
|
prval () = fold@ l
|
|
in () end
|
|
|
|
fun {a:vt@ype} pop_last {n:pos} (l: &list_vt(a,n) >> list_vt(a,n-1)): a =
|
|
let
|
|
val+ @list_vt_cons(head, tail) = l
|
|
in case tail of
|
|
| list_vt_cons _ => let
|
|
val res = pop_last tail
|
|
prval () = fold@ l
|
|
in res end
|
|
| ~list_vt_nil() => let
|
|
val head = head
|
|
// Deallocate empty datavtype nodes with free@
|
|
val () = free@{..}{0} l
|
|
val () = l := list_vt_nil()
|
|
in head end
|
|
/** Equivalently:
|
|
* | ~list_vt_nil() => let
|
|
* prval () = fold@ l
|
|
* val+ ~list_vt_cons(head, ~list_vt_nil()) = l
|
|
* val () = l := list_vt_nil()
|
|
* in head end
|
|
*/
|
|
end
|
|
|
|
// "holes" (ie uninitialized memory) can be created with _ on the RHS
|
|
// This function uses destination-passing-style to copy the list in a single
|
|
// tail-recursive pass.
|
|
fn {a:t@ype} copy_list {n:nat} (l: !list_vt(a, n)): list_vt(a, n) =
|
|
let
|
|
var res: ptr
|
|
fun loop {k:nat} (l: !list_vt(a, k), hole: &ptr? >> list_vt(a, k)): void =
|
|
case l of
|
|
| list_vt_nil() => hole := list_vt_nil
|
|
| list_vt_cons(first, rest) => let
|
|
val () = hole := list_vt_cons{..}{k-1}(first, _)
|
|
// ^ on RHS: a hole
|
|
val+list_vt_cons(_, new_hole) = hole
|
|
// ^ on LHS: wildcard pattern (not a hole)
|
|
val () = loop (rest, new_hole)
|
|
prval () = fold@ hole
|
|
in () end
|
|
val () = loop (l, res)
|
|
in res end
|
|
|
|
// Reverse a linked-list *in place* -- no allocations or frees
|
|
fn {a:vt@ype} in_place_reverse {n:nat} (l: list_vt(a, n)): list_vt(a, n) =
|
|
let
|
|
fun loop {k:nat} (l: list_vt(a, n-k), acc: list_vt(a, k)): list_vt(a, n) =
|
|
case l of
|
|
| ~list_vt_nil() => acc
|
|
| @list_vt_cons(x, tail) => let
|
|
val rest = replace(tail, acc)
|
|
// the node 'l' is now part of acc instead of the original list
|
|
prval () = fold@ l
|
|
in loop (rest, l) end
|
|
in loop (l, list_vt_nil) end
|
|
|
|
|
|
/*************** Part 6: miscellaneous extras ****************/
|
|
|
|
// a record
|
|
// Point has type 't@ype'
|
|
typedef Point = @{ x= int, y= int }
|
|
val origin: Point = @{ x= 0, y= 0 }
|
|
|
|
// tuples and records are normally unboxed, but there are boxed variants
|
|
// BoxedPoint has type 'type'
|
|
typedef BoxedPoint = '{ x= int, y= int }
|
|
val boxed_pair: '(int,int) = '(5, 3)
|
|
|
|
// When passing a pair as the single argument to a function, it needs to be
|
|
// written @(a,b) to avoid ambiguity with multi-argument functions
|
|
val six_plus_seven = let
|
|
fun sum_of_pair (pair: (int,int)) = pair.0 + pair.1
|
|
in sum_of_pair @(6, 7) end
|
|
|
|
// When a constructor has no associated data, such as None(), the parentheses
|
|
// are optional in expressions. However, they are mandatory in patterns
|
|
fn inc_option (opt: Option int) =
|
|
case opt of
|
|
| Some(x) => Some(x+1)
|
|
| None() => None
|
|
|
|
// ATS has a simple FFI, since it compiles to C and (mostly) uses the C ABI
|
|
%{
|
|
// Inline C code
|
|
int scanf_wrapper(void *format, void *value) {
|
|
return scanf((char *) format, (int *) value);
|
|
}
|
|
%}
|
|
// If you wanted to, you could define a custom dataviewtype more accurately
|
|
// describing the result of scanf
|
|
extern fn scanf (format: string, value: &int): int = "scanf_wrapper"
|
|
|
|
fn get_input_number (): Option int =
|
|
let
|
|
var x: int = 0
|
|
in
|
|
if scanf("%d\n", x) = 1
|
|
then Some(x)
|
|
else None
|
|
end
|
|
|
|
// extern is also used for separate declarations and definitions
|
|
extern fn say_hi (): void
|
|
// later on, or in another file:
|
|
implement say_hi () = print "hi\n"
|
|
|
|
// if you implement main0, it will run as the main function
|
|
// implmnt is an alias for implement
|
|
implmnt main0 () = ()
|
|
|
|
// as well as for axioms:
|
|
extern praxi view_id {a:view} (x: a): a
|
|
// you don't need to actually implement the axioms, but you could
|
|
primplmnt view_id x = x
|
|
// primplmnt is an alias for primplement
|
|
|
|
// Some standard aliases are:
|
|
// List0(a) = [n:nat] list(a,n) and List0_vt(a) = [n:nat] list_vt(a,n)
|
|
// t0p = t@ype and vt0p = vt@ype
|
|
fun {a:t0p} append (xs: List0 a, ys: List0 a): List0 a =
|
|
case xs of
|
|
| list_nil() => ys
|
|
| list_cons(x, xs) => list_cons(x, append(xs, ys))
|
|
|
|
// there are many ways of doing things after one another
|
|
val let_in_example = let
|
|
val () = print "thing one\n"
|
|
val () = print "thing two\n"
|
|
in () end
|
|
|
|
val parens_example = (print "thing one\n"; print "thing two\n")
|
|
|
|
val begin_end_example = begin
|
|
print "thing one\n";
|
|
print "thing two\n"; // optional trailing semicolon
|
|
end
|
|
|
|
// and many ways to use local variables
|
|
fun times_four_let x =
|
|
let
|
|
fun times_two (x: int) = x * 2
|
|
in times_two (times_two x) end
|
|
|
|
local
|
|
fun times_two (x: int) = x * 2
|
|
in
|
|
fun times_four_local x = times_two (times_two x)
|
|
end
|
|
|
|
fun times_four_where x = times_two (times_two x)
|
|
where {
|
|
fun times_two (x: int) = x * 2
|
|
}
|
|
|
|
//// The last kind of comment in ATS is an end-of-file comment.
|
|
|
|
Anything between the four slashes and the end of the file is ignored.
|
|
|
|
Have fun with ATS!
|
|
```
|
|
|
|
## Learn more
|
|
|
|
This isn't all there is to ATS -- notably, some core features like closures and
|
|
the effect system are left out, as well as other less type-y stuff like modules
|
|
and the build system. If you'd like to write these sections yourself,
|
|
contributions would be welcome!
|
|
|
|
To learn more about ATS, visit the [ATS website](http://www.ats-lang.org/), in
|
|
particular the [documentation page](http://www.ats-lang.org/Documents.html).
|
|
|