Contents
ipso
language reference
Execution
ipso
will look for an IO action named main
when called from the command line:
$ cat > example.ipso <<EOF
main : IO ()
main = print "hello"
EOF
$ ipso example.ipso
hello
This behaviour can be overridden with --run
:
$ cat > example.ipso <<EOF
sayHello : IO ()
sayHello = print "hello"
EOF
$ ipso example.ipso --run sayHello
hello
Comments
# this is a single line comment
Indentation
ipso
uses spaces (U+0020) for indentation.
Line Joining
An indented line is considered a continuation of the preceding line.
Blocks
The first line of a block must be indented further than the block’s opening keyword. Subsequent lines of a block must have indentation equal to its first line.
Keywords that open blocks:
Examples
Correct:
: IO ()
main = comp
main "hello"
println "world" println
: IO ()
main =
main comp
"hello"
println "world" println
: IO ()
main =
main comp
"hello"
println
println"world"
: IO ()
main = case x of
main -> 0
A a -> 1 B b
: IO ()
main =
main case x of
-> 0
A a -> 1 B b
case x of
->
A a 0
->
B b 1
Incorrect:
main : IO ()
main = comp
println "hello"
println "world"
main : IO ()
main =
comp
println "hello"
println "world"
case x of
-> 0
A a -> 1 B b
Declarations
Definitions
: Int
x = 1
x
: String
y = "hello" y
Imports
Basic Imports
import char
import string
: String -> String
loudly = string.map char.to_upper loudly
Renaming Imports
import char as c
import string as s
: String -> String
loudly = s.map c.to_upper loudly
Selective Imports
from char import to_upper
from string import map
: String -> String
loudly = map to_upper loudly
Wildcard Imports
from char import *
from string import *
: String -> String
loudly = map to_upper loudly
Pattern Matching
Records
> x = { a = "hi", b = true, c = 1 }
: { a : String, b : Bool, c : Int }
x > case x of
. { a, b, c } -> b
.
true
> case x of
. { b, ..rest } -> rest
.
{ a = "hi", c = 1 }
Variants
> x = None ()
: forall r. (| None : (), r |)
x > case x of
. None () -> 1
. _ -> 2
.
1
> x = None () : (| None : () |)
: (| None : () |)
x > case x of
. None () -> 1
.
1
> x = Ok (Ok 99)
: forall r1 r2. (| Ok : (| Ok : Int, r1 |), r2 |)
x > case x of
. Ok (Ok a) -> a
. _ -> 0
.
99
Incomplete pattern matches are not (yet) reported by the type checker:
> x = None ()
: forall r. (| None : (), r |)
x > case x of
. None () -> 1
.
1
Arrays
> x = [1, 2, 3]
: Array Int
x > case x of
. [a, b, c] -> a + b + c
. _ -> 0
.
6
Literals
> case 'a' of
. 'b' -> "b"
. 'a' -> "a"
. _ -> "something else"
.
"a"
> case 1 of
. 0 -> "0"
. 1 -> "1"
. _ -> "something else"
.
"1"
> case "true" of
. "false" -> 0
. "true" -> 1
. _ -> 2
.
1
Let Bindings
> let x = 1 in
. let y = 2 in
. x + y
3
Computation Expressions
See also: IO
> :t comp
. bind x <- readln
. let twoXs = "$x $x"
. print twoXs
.
() IO
> :t comp
. bind x <- readln
. return x
.
IO String
Command Literals
See also: Commands
> :t `ls -laR`
Cmd
> cmd.run `echo "hello!"`
hello!
> cmd.run ``
Interpolation
> let arg = "hi"
> `echo $arg`
`echo hi`
> let args = ["hello", "world"]
> `echo $..args`
`echo hello world`
> `echo ${ string.join " " ["hello", "world"] }`
`echo "hello world"`
> `echo $..{ ["a a", "b b"] ++ ["c c", "d d"] }`
`echo "a a" "b b" "c c" "d d"`
> :type \a b c d -> `echo $a $..b $c $..d`
-> Array String -> String -> Array String -> Cmd String
Operators
== : Eq a => a -> a -> Bool
!= : Eq a => a -> a -> Bool
<= : Ord a => a -> a -> Bool
< : Ord a => a -> a -> Bool
>= : Ord a => a -> a -> Bool
> : Ord a => a -> a -> Bool
&& : Bool -> Bool -> Bool
|| : Bool -> Bool -> Bool
+ : Int -> Int -> Int
- : Int -> Int -> Int
* : Int -> Int -> Int
/ : Int -> Int -> Int
++ : Array a -> Array a -> Array a
<| : (a -> b) -> a -> b
|> : a -> (a -> b) -> b
Datatypes
Unit
> :type ()
()
Booleans
> :type true
Bool
> :type false
Bool
> if true then "yes" else "no"
"yes"
> if false then "yes" else "no"
"no"
Builtins
not : Bool -> Bool
Integers
> :type 0
Int
> :type 1
Int
> :type -1
Int
Builtins
module int where
: Int -> Int -> Bool
eq
: Int -> String
toString
: Int -> Int -> Int
mod
: Int -> Int -> Int
min
: Int -> Int -> Int
max
# Parse a binary number.
#
# Parses strings that match: `-?(0b)?[01]+`
: String -> (| Some : Int, None : () |)
parseBin
# Parse an octal number.
#
# Parses strings that match: `-?(0o)?[0-7]+`
: String -> (| Some : Int, None : () |)
parseOct
# Parse a decimal (base 10) number.
#
# Parses strings that match: `-?[0-9]+`
: String -> (| Some : Int, None : () |)
parseDec
# Parse a hexadecimal number (case insensitive).
#
#Parses strings that match: `-?(0x)?[0-9a-fA-F]+`
: String -> (| Some : Int, None : () |) parseHex
Characters
The Char
type represents a unicode code point.
> :type 'a'
Char
> :type '🤩'
Char
Builtins
module char where
: Char -> Char -> Bool
eq
: Char -> String toString
Strings
The String
type is a UTF-8 encoded sequence of bytes.
> :type "hello"
String
Interpolation
> x = "hello"
: String
x > "$x world"
"hello world"
> x = ["a", "b", "c"]
: Array String
x > "joined: ${string.join ", " x}"
"joined: a, b, c"
Builtins
module string where
: String -> Bytes
toUtf8
: String -> String -> Bool
eq
: (a -> Char -> a) -> a -> String -> a
foldl
: (Char -> Bool) -> String -> String
filter
: String -> Array Char
toChars
: Array Char -> String
fromChars
: String -> String -> Array String
split
: Char -> String -> Array String
splitc
: String -> Array String -> String
join
# `parts delimiter value` returns the sections of `value` that don't contain
# the string `delimiter`.
#
# Passing an empty string as `delimiter` is a runtime error.
#
# # Examples
#
# * `parts "::" "" == []`
# * `parts "::" "a" == ["a"]`
# * `parts "::" "a::b" == ["a", "b"]`
# * `parts "::" "a::b::" == ["a", "b"]`
# * `parts "!=" "a != b" == ["a ", " b"]`
: String -> String -> Array String
parts
# `partsc delimiter value` returns the sections of `value` that don't contain
# the character `delimiter`.
#
# # Examples
#
# * `partsc ' ' "" == []`
# * `partsc ' ' "hello" == ["hello"]`
# * `partsc ' ' "hello world" == ["hello", "world"]`
# * `partsc ' ' " hello world " == ["hello", "world"]`
: Char -> String -> Array String
partsc
# Remove characters from both ends of a string, according to a predicate.
: (Char -> Bool) -> String -> String
trimp
# Remove a specific character from both ends of a string.
#
# `trimc c1 = trimp (\c2 -> c2 == c1)`
: Char -> String -> String
trimc
# Remove [whitespace](https://en.wikipedia.org/wiki/Unicode_character_property#Whitespace) from both ends of a string.
: String -> String
trim
# Removes the first argument (`prefix`) from the start of the second argument (`value`).
#
# Returns `None ()` if `value` doesn't start with `prefix`.
#
# # Examples
#
# * `stripPrefix "hello" "hello world" == Some " world"`
# * `stripPrefix "helicopter" "hello world" == None ()`
: String -> String -> (| Some : String, None : () |)
stripPrefix
# Returns `true` when the second argument (`value`) starts with the first argument (`prefix`).
#
# # Examples
#
# * `startsWith "hello" "hello world" == true`
# * `startsWith "helicopter" "hello world" == false`
: String -> String -> Bool startsWith
Functions
> :type \x -> x
-> t0 forall t0. t0
> :type \x y -> x + y
-> Int -> Int Int
> f x = x + 1
: Int -> Int
f > f 2
3
> f { x, y } = x + y
: { x : Int, y : Int } -> Int f
> :t \{ x, y } -> x + y
{ x : Int, y : Int } -> Int
> :t \{ x, ..rest } -> x + rest.y + rest.z
{ x : Int, y : Int, z : Int } -> Int
Arrays
> :type [1, 2, 3]
Array Int
> :type [1, true, 3]
(repl):1:5: error: expected 'Int', got 'Bool'
|
1 | [1, true, 3] | ^^^^
Builtins
module array where
: (a -> a -> Bool) -> Array a -> Array a -> Bool
eq
: (b -> a -> b) -> b -> Array a -> b
foldl
: Int -> (Int -> a) -> Array a
generate
: Array a -> Int
length
# Get the value at a given index in the array.
#
# Returns `None ()` when the index is out of bounds.
: Int -> Array a -> (| Some : a, None : () |)
get
# Like `get`, but crashes when the index is out of bounds.
: Int -> Array a -> a
get!
# Get the first value in the array.
#
# Returns `None ()` when the array is empty.
: Array -> (| Some : a, None : () |)
first
# Get the last value in the array.
#
# Returns `None ()` when the array is empty.
: Array -> (| Some : a, None : () |)
last
: Int -> Int -> Array a -> Array a
slice
# Swap two elements in an array.
#
# Crashes when either index is out of bounds.
: Int -> Int -> Array a -> Array a
swap
: Array a -> a -> Array a
snoc
: (a -> b) -> Array a -> Array b
map
: (a -> Array b) -> Array a -> Array b
flatMap
# Keep only the values that satisfy a predicate.
: (a -> Bool) -> Array a -> Array a
filter
# Keep and transform the values that satisfy some condition.
: (a -> (| Some : b, None : () |)) -> Array a -> Array b
filterMap
:
unfoldr ->
s (s -> (| Step : { value : a, next : s }, Skip : { next : s }, Done : () |)) ->
Array a
# Return the first value of an array that satisfies a predicate.
: (a -> Bool) -> Array a -> (| Some : a, None : () |)
find
# Return and transform the first value of an array that satisfies some condition.
: (a -> (| Some : b, None : () |)) -> Array a -> (| Some : b, None : () |)
findMap
: Array Int -> Int
sum
: (a -> Bool) -> Array a -> Bool
any
: Array a -> (a -> IO ()) -> IO () each_
Byte Arrays
> :kind Bytes
Type
Records
Construction
> :t { x = 1, y = true }
{ x : Int, y : Bool }
> a = 1
: Int
a > b = true
: Bool
b > { a, b }
{ a = 1, b = True }
Projection
> x = { a = "hello", b = false }
: { a : String, b : Bool }
x > x.a
"hello"
> x.b
false
Extension
> rest = { more = false, words = ["a", "b"] }
: { more : Bool, words : Array String }
rest > :t { some = "some", ..rest }
{ some : String, more : Bool, words : Array String }
Variants
Construction
> :t None
-> (| None : a, r |) forall a r. a
Extension
> :t \x -> (| A, ..x |)
(| r |) -> (| A : a, r |)
IO
> :kind IO
-> Type Type
> comp
. bind line <- readln
. print line
hello hello
Builtins
module io where
: a -> IO a
pure
: (a -> b) -> IO a -> IO b
map
: IO a -> (a -> IO b) -> IO b andThen
: String -> IO ()
println
: String -> IO ()
print
# Like `println`, but outputs to stderr.
: String -> IO ()
eprintln
# Like `print`, but outputs to stderr.
: String -> IO ()
eprint
# `dprintln x = println <| debug x`
: Debug a => a -> IO ()
dprintln
# `dprint x = print <| debug x`
: Debug a => a -> IO () dprint
: IO String readln
Commands
> :kind Cmd
Type
> :type `echo "hello, world!"`
Cmd
Builtins
module cmd where
: Cmd -> IO ()
run
# Run a command and return its exit status.
: Cmd -> IO (| Success : (), Failure : Int |)
try
# Run a command, capturing its `stdout` as a string.
: Cmd -> IO String
read
# Run a command, capturing the lines it writes to `stdout`.
#
# `lines command` is equivalent to `io.map (string.splitc '\n') (cmd.read command)`
: Cmd -> IO (Array String)
lines
# Run a command and execute an `IO` action for each line written to `stdout`.
#
# `eachline_ cmd f` is equivalent to `io.andThen (cmd.lines cmd) (\lines -> array.each_ lines f)`
: Cmd -> (String -> IO ()) -> IO ()
eachline_
: Cmd -> String show
Type Classes
Equality
class Eq a where
: a -> a -> Bool
eq
: Eq a => a -> a -> Bool neq
instance Eq Bool
instance Eq Char
instance Eq String
instance Eq Int
instance Eq a => Eq (Array a)
Comparison
class Eq a => Ord a where
: a -> a -> (| Less : (), Equal : (), Greater : () |)
compare
: a -> a -> Bool
lte
: a -> a -> Bool
lt
: a -> a -> Bool
gte
: a -> a -> Bool gt
instance Ord Bool
instance Ord Char
instance Ord String
instance Ord Int
instance Ord a => Ord (Array a)
Debugging
class Debug a where
: a -> String
debug
instance Debug ()
instance Debug Bool
instance Debug Int
instance Debug Char
instance Debug String
instance Debug Cmd
instance Debug a => Debug (Array a)
instance DebugRecordFields a => Debug { a }
instance DebugVariantCtor a => Debug (| a |)
: DebugRecordFields a => { a } -> Array { field : String, value : String }
debugRecordFields : DebugVariantCtor a => (| a |) -> { ctor : String, value : String } debugVariantCtor
Standard Library
env
module env where
# The current program's name.
#
# Example:
#
# ```
# $ cat > test.ipso <<EOF
# main : IO ()
# main =
# comp
# bind program <- env.program
# println program
# EOF
# $ ipso test.ipso -- a b c
# test.ipso
# ```
: IO String
program
# The arguments passed to the current program.
#
# Example:
#
# ```
# $ cat > test.ipso <<EOF
# main : IO ()
# main =
# comp
# bind args <- env.args
# println <| debug args
# EOF
# $ ipso test.ipso -- a b c
# ["a", "b", "c"]
# ```
: IO (Array String)
args
: String -> IO (| Some : String, None : () |)
getvar
: String -> IO String
getvar!
: String -> String -> IO () setvar
exit
module exit where
: IO a
success
: IO a
failure
: Int -> IO a with
file
module file where
: String -> IO String
read
: String -> String -> IO ()
write
: String -> String -> IO () append
path
module path where
: String -> IO Bool exists
Grammar
ident ::=
ident_start ident_continue*
ident_start ::= (lowercase ASCII alphabetic characters)
ident_continue ::=
(ASCII alphanumeric characters) |
'_' |
'!' |
'?'
ctor ::=
ctor_start ctor_continue*
ctor_start ::= (uppercase ASCII alphabetic characters)
ctor_continue ::=
(ASCII alphanumeric characters) |
'_'
type ::=
type_app (type_arrow type)*
type_arrow ::=
'->' |
'=>'
type_app ::=
type_atom+
type_atom ::=
type_record |
type_variant |
ident |
ctor |
'(' type ')'
type_record ::=
'{' type_record_content '}'
type_record_content ::=
epsilon |
type_signature (',' type_signature)* [',' ident] |
ident
type_variant ::=
'(|' type_variant_content '|)'
type_variant_content ::=
epsilon |
[type_variant_item ('|' type_variant_item)* ['|' ident]] |
ident
type_variant_item ::=
ctor [':' type]
pattern ::=
pattern_atom |
ctor pattern_atom
pattern_atom ::=
ident |
int |
char |
string |
'{' [record_pattern] '}' |
array_pattern |
'_' |
'(' [pattern] ')'
record_pattern ::=
ident (',' ident)* [',' '..' ident] |
'..' ident |
array_pattern ::=
'[' [ident (',' ident)*] ']'
expr ::=
lambda |
case |
ifthenelse |
let |
comp |
binop
lambda ::=
'\' pattern '->' expr
case ::=
'case' expr 'of' case_branch*
case_branch ::=
ctor '->' expr
ifthenelse ::=
'if' expr 'then' expr 'else' expr
let ::=
'let' ident '=' expr 'in' expr
comp_line ::=
expr
'bind' ident '<-' expr
'return' expr
comp ::=
'comp' comp_line+
binop ::=
app (operator app)*
operator ::=
'=' (operator_symbol | '=')+
operator_symbol+
operator_symbol ::=
'+' |
'-' |
'*' |
'/' |
'<' |
'>' |
'^' |
'%' |
'|' |
'&' |
'!'
app ::=
project+
project ::=
atom ('.' ident)*
atom ::=
bool |
int |
char |
string |
array |
record |
ident |
ctor |
cmd |
'(' expr ')'
bool ::=
'true' |
'false'
int ::=
(ASCII numeric character)+
char ::=
"'" (any unicode code point except single quote) "'" |
"'" '\' (single quote) "'"
string ::=
'"' string_part* '"'
string_part ::=
string_char+ |
string_interpolate
string_char ::=
(any unicode code point except double quote or dollar sign) |
'\' '"' |
'\' '$'
string_interpolate ::=
'$' '{' expr '}'
array ::=
'[' [expr (',' expr)*] ']'
record ::=
'{' record_content '}'
record_content ::=
epsilon |
record_item (',' record_item)* [',' '..' atom] |
'..' atom
record_item ::=
ident '=' expr
cmd_char ::=
(any ascii character except '`', '$', '"', '\')
'\' '`'
'\' '$'
'\' '"'
'\' '\'
cmd_string_part ::=
cmd_char+
'$' ident
'$' '{' expr '}'
cmd_part ::=
cmd_string_part+
'$..' ident
'$..{' expr '}'
cmd ::=
'`' cmd_part* '`'
decl ::=
import |
type_signature |
definition |
instance |
class |
type_alias
import ::=
'import' module_name ['as' module_name] |
'from' module_name 'import' from_imports
module_name ::=
ident ('.' ident)*
from_imports ::=
'*' |
ident (',' ident)*
type_signature ::=
ident ':' type
definition ::=
ident [pattern] '=' expr
instance ::=
'instance' type 'where' instance_member*
instance_member ::=
[type_signature] definition
class ::=
'class' type 'where' class_member*
class_member ::=
type_signature
type_alias ::=
'type' type '=' type