Language Guide
1.0 Installing
I recommend downloading the APK file from the bin/
directory in the github repo.
The editor also works on linux. Clone the repo. Run main.py
in python3.10
(or higher version) and install any libraries python complains about.
Note: On first startup the app will some time to load, while the library and example files are downloaded from the github repo. The loading time depends on your internet connection. Put a file into
Internal Storage/Android/data/org.test.myapp/files/
(over USB) if you want to stop the app from installing the default files.
You can also build the project yourself using the buildozer tool.
1.1 Data Types
PocketML uses Sum types like most other statically typed functional languages:
data Maybe a
| Just a
| Nothing
You can also declare that a type exists, for example to represent a value from a function implemented in python (see “Hacking / Python interop” page).
data PythonObject;
Type aliases can be used to abbreviate the names of other data types:
type Mb a = Maybe a;
1.2 Branching
Pattern matching is based on the case
keyword and can match basic data types:
case 1
| 1 -> "One"
| _ -> "Something else"
And sum types (except for numpy arrays):
case Just 1
| Just x -> print x
| Nothing -> ()
Lists are also sum types:
case [1,2,3]
| Cons x _ -> print x
| Nil -> print "empty..."
Use if-then-else for branching:
let f x = if x then "True!" else "false :(";
f True
When programming with side effects, an else branch may not be needed. The if-then expression must always return Unit.
let f : Bool -> Unit;
let f b = if b then print "True!";
...
1.3 Do-Syntax
When many functions need to be executed one after another, for example to cause side effects, the do-syntax can be used.
let f _ = do
print "1"
print "2"
print "3"
launch_missiles ()
;
Note: Do not confuse this do syntax with monadic do-notation in Haskell! This is more of a C-like block (e.g.
{ ...; ...; }
). The do-syntax is not perfect and might fail to parse in some situations. When in doubt uselet _ = a (); let _ = b (); ...
1.4 Lists
Lists can be created like in the following example:
import std;
print [1, 2, 3]
# => (Cons 1 (Cons 2 (Cons 3 Nil)))
Numpy arrays can be created using the @(x1, x2, x3, ...)
syntax:
print (@(1, 2) + @(3, 4)) # => [4. 6.]
They have type Vec
.
1.5 Tuples and Records
PocketML supports both tuples and records. It is best to use records and tuples sparingly, as custom data types carry more information and are more strongly typed.
type Point = (Number, Number);
type Person =
{ name: String
, age: Number
, location: Point };
Note that PocketML does not support tuple and record pattern matching yet!
Records also support a sort of weak row-polymorphism.
let getX : { x : a } -> a;
let getX r = r.x;
print $ getX {x=10, y=20}
Generally a record {x : a, y : b}
and a record {y : b}
unify. That also means that the following example only fails at runtime.
let getX : { x : a, y : b } -> a;
let getX r = r.x;
print $ getX {y=20}
Note: The section Python Interop has an example of how this can be used to implement named default arguments.
The standard library lib.std
has functions for updating records:
import lib.std;
let myrec = {x=1,y=2,z=3};
do
print (with { x = 22 } myrec)
print (recordMap (\r -> with {x=r.x+1} r) myrec)
1.6 Functions, recursion and let
Variables are generally introduced using the let
keyword. Let declarations can be used to introduce a variables type before defining it:
let pi : Number;
let pi = 3;
Functions can be introduced using \
and are anonymous. To create recursive functions, the rec
keyword or an explicit type annotation is required:
let rec sum = \x -> case x
| Nil -> 0
| Cons x xs -> add x (sum xs);
print (sum [1,2,3,4])
Or alternatively:
let sum : (List Number) -> Number
let sum = \case
| Nil -> 0
| Cons x xs -> add x (sum xs);
print (sum [1,2,3,4])
Note: The above example uses the
\case
notation which is equivalent to\x -> case x ...
.
Functions can also be introduced using let:
let greet x = print2 "Hello," x;
greet "there!"
1.7 Modules
PocketML projects are organized into modules. A module exports variables, types and type aliases. The explicit module declaration can limit what is exported:
Note: Constructors must be exported explicitly
let greet x = print2 "Hi," x;
let pi = 4;
data MyType =
MyConstructor Number;
module
( greet
, pi
, type MyType
, MyConstructor
)
Modules can also use (*)
to export all types and variables.
let greet x = print2 "Hi," x;
let pi = 4;
module (*)
Modules inside a directory can be addressed using .
:
import directory.mymodule;
Imports can also be selective (only importing some names from a module). Modules can also be aliased (given an explicit name).
import lib.math (sin) as Math;
import lib.calculus as Calc;
import lib.std;
print $ (Calc.diff sin 1) Math.pi
1.8 Doc comments
The editor has a builtin search panel for looking up types or searching for a function with a certain type. Any comments before the semicolon in a declaration will be included as a doc comment:
data List a
= Cons a (List a)
| Nil
# The default list type generated
# by `[...]`
;
let mkDict : a -> Dict b
# WARNING: `a` should always be a record.
;
type Color = Vec
# Just a vector...
;
Most of the standard library is written in a self-documenting way to save on excessive comment clutter when searching in the doc panel.
If you want to hide a declaration from the search panel, you can include the --hide
flag in the definition comment:
data List a
# my own list :)
# --hide
;
For library authors: The gen_lib_docs.py
script generates markdown doc files from all .ml
modules in the examples/lib/
directory. The module description can be defined using a ##
-comment. Markdown can be inserted using ###
:
## My module. It provides T!
### ### Definitions
data T = C Num;
### ### Functions
let unT : T -> Num;
let unT = \case C n -> n;
For more information consult the “Docs” tab in the editor app that is available for android and linux.