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 commentIndentation
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:
main : IO ()
main = comp
println "hello"
println "world"main : IO ()
main =
comp
println "hello"
println "world"main : IO ()
main =
comp
println "hello"
println
"world"main : IO ()
main = case x of
A a -> 0
B b -> 1main : IO ()
main =
case x of
A a -> 0
B b -> 1case x of
A a ->
0
B b ->
1Incorrect:
main : IO ()
main = comp
println "hello"
println "world"
main : IO ()
main =
comp
println "hello"
println "world"
case x of
A a -> 0
B b -> 1Declarations
Definitions
x : Int
x = 1
y : String
y = "hello"Imports
Basic Imports
import char
import string
loudly : String -> String
loudly = string.map char.to_upperRenaming Imports
import char as c
import string as s
loudly : String -> String
loudly = s.map c.to_upperSelective Imports
from char import to_upper
from string import map
loudly : String -> String
loudly = map to_upperWildcard Imports
from char import *
from string import *
loudly : String -> String
loudly = map to_upperPattern Matching
Records
> x = { a = "hi", b = true, c = 1 }
x : { a : String, b : Bool, c : Int }
> case x of
. { a, b, c } -> b
.
true
> case x of
. { b, ..rest } -> rest
.
{ a = "hi", c = 1 }Variants
> x = None ()
x : forall r. (| None : (), r |)
> case x of
. None () -> 1
. _ -> 2
.
1> x = None () : (| None : () |)
x : (| None : () |)
> case x of
. None () -> 1
.
1> x = Ok (Ok 99)
x : forall r1 r2. (| Ok : (| Ok : Int, r1 |), r2 |)
> case x of
. Ok (Ok a) -> a
. _ -> 0
.
99Incomplete pattern matches are not (yet) reported by the type checker:
> x = None ()
x : forall r. (| None : (), r |)
> case x of
. None () -> 1
.
1Arrays
> x = [1, 2, 3]
x : Array Int
> case x of
. [a, b, c] -> a + b + c
. _ -> 0
.
6Literals
> 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
.
1Let Bindings
> let x = 1 in
. let y = 2 in
. x + y
3Computation Expressions
See also: IO
> :t comp
. bind x <- readln
. let twoXs = "$x $x"
. print twoXs
.
IO ()> :t comp
. bind x <- readln
. return x
.
IO StringCommand 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`
String -> Array String -> String -> Array String -> CmdOperators
== : 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) -> bDatatypes
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
IntBuiltins
module int where
eq : Int -> Int -> Bool
toString : Int -> String
mod : Int -> Int -> Int
min : Int -> Int -> Int
max : Int -> Int -> Int
# Parse a binary number.
#
# Parses strings that match: `-?(0b)?[01]+`
parseBin : String -> (| Some : Int, None : () |)
# Parse an octal number.
#
# Parses strings that match: `-?(0o)?[0-7]+`
parseOct : String -> (| Some : Int, None : () |)
# Parse a decimal (base 10) number.
#
# Parses strings that match: `-?[0-9]+`
parseDec : String -> (| Some : Int, None : () |)
# Parse a hexadecimal number (case insensitive).
#
#Parses strings that match: `-?(0x)?[0-9a-fA-F]+`
parseHex : String -> (| Some : Int, None : () |) Characters
The Char type represents a unicode code point.
> :type 'a'
Char> :type '🤩'
CharBuiltins
module char where
eq : Char -> Char -> Bool
toString : Char -> StringStrings
The String type is a UTF-8 encoded sequence of bytes.
> :type "hello"
StringInterpolation
> x = "hello"
x : String
> "$x world"
"hello world"> x = ["a", "b", "c"]
x : Array String
> "joined: ${string.join ", " x}"
"joined: a, b, c"Builtins
module string where
toUtf8 : String -> Bytes
eq : String -> String -> Bool
foldl : (a -> Char -> a) -> a -> String -> a
filter : (Char -> Bool) -> String -> String
toChars : String -> Array Char
fromChars : Array Char -> String
split : String -> String -> Array String
splitc : Char -> String -> Array String
join : String -> Array String -> String
# `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"]`
parts : String -> String -> Array String
# `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"]`
partsc : Char -> String -> Array String
# Remove characters from both ends of a string, according to a predicate.
trimp : (Char -> Bool) -> String -> String
# Remove a specific character from both ends of a string.
#
# `trimc c1 = trimp (\c2 -> c2 == c1)`
trimc : Char -> String -> String
# Remove [whitespace](https://en.wikipedia.org/wiki/Unicode_character_property#Whitespace) from both ends of a string.
trim : String -> String
# 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 ()`
stripPrefix : String -> String -> (| Some : String, None : () |)
# Returns `true` when the second argument (`value`) starts with the first argument (`prefix`).
#
# # Examples
#
# * `startsWith "hello" "hello world" == true`
# * `startsWith "helicopter" "hello world" == false`
startsWith : String -> String -> BoolFunctions
> :type \x -> x
forall t0. t0 -> t0> :type \x y -> x + y
Int -> Int -> Int> f x = x + 1
f : Int -> Int
> f 2
3> f { x, y } = x + y
f : { x : Int, y : Int } -> Int> :t \{ x, y } -> x + y
{ x : Int, y : Int } -> Int> :t \{ x, ..rest } -> x + rest.y + rest.z
{ x : Int, y : Int, z : Int } -> IntArrays
> :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
eq : (a -> a -> Bool) -> Array a -> Array a -> Bool
foldl : (b -> a -> b) -> b -> Array a -> b
generate : Int -> (Int -> a) -> Array a
length : Array a -> Int
# Get the value at a given index in the array.
#
# Returns `None ()` when the index is out of bounds.
get : Int -> Array a -> (| Some : a, None : () |)
# Like `get`, but crashes when the index is out of bounds.
get! : Int -> Array a -> a
# Get the first value in the array.
#
# Returns `None ()` when the array is empty.
first : Array -> (| Some : a, None : () |)
# Get the last value in the array.
#
# Returns `None ()` when the array is empty.
last : Array -> (| Some : a, None : () |)
slice : Int -> Int -> Array a -> Array a
# Swap two elements in an array.
#
# Crashes when either index is out of bounds.
swap : Int -> Int -> Array a -> Array a
snoc : Array a -> a -> Array a
map : (a -> b) -> Array a -> Array b
flatMap : (a -> Array b) -> Array a -> Array b
# Keep only the values that satisfy a predicate.
filter : (a -> Bool) -> Array a -> Array a
# Keep and transform the values that satisfy some condition.
filterMap : (a -> (| Some : b, None : () |)) -> Array a -> Array b
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.
find : (a -> Bool) -> Array a -> (| Some : a, None : () |)
# Return and transform the first value of an array that satisfies some condition.
findMap : (a -> (| Some : b, None : () |)) -> Array a -> (| Some : b, None : () |)
sum : Array Int -> Int
any : (a -> Bool) -> Array a -> Bool
each_ : Array a -> (a -> IO ()) -> IO ()Byte Arrays
> :kind Bytes
TypeRecords
Construction
> :t { x = 1, y = true }
{ x : Int, y : Bool }> a = 1
a : Int
> b = true
b : Bool
> { a, b }
{ a = 1, b = True }Projection
> x = { a = "hello", b = false }
x : { a : String, b : Bool }
> x.a
"hello"
> x.b
falseExtension
> rest = { more = false, words = ["a", "b"] }
rest : { more : Bool, words : Array String }
> :t { some = "some", ..rest }
{ some : String, more : Bool, words : Array String }Variants
Construction
> :t None
forall a r. a -> (| None : a, r |)Extension
> :t \x -> (| A, ..x |)
(| r |) -> (| A : a, r |)IO
> :kind IO
Type -> Type> comp
. bind line <- readln
. print line
hello
helloBuiltins
module io where
pure : a -> IO a
map : (a -> b) -> IO a -> IO b
andThen : IO a -> (a -> IO b) -> IO bprintln : String -> IO ()
print : String -> IO ()
# Like `println`, but outputs to stderr.
eprintln : String -> IO ()
# Like `print`, but outputs to stderr.
eprint : String -> IO ()
# `dprintln x = println <| debug x`
dprintln : Debug a => a -> IO ()
# `dprint x = print <| debug x`
dprint : Debug a => a -> IO ()readln : IO StringCommands
> :kind Cmd
Type> :type `echo "hello, world!"`
CmdBuiltins
module cmd where
run : Cmd -> IO ()
# Run a command and return its exit status.
try : Cmd -> IO (| Success : (), Failure : Int |)
# Run a command, capturing its `stdout` as a string.
read : Cmd -> IO String
# Run a command, capturing the lines it writes to `stdout`.
#
# `lines command` is equivalent to `io.map (string.splitc '\n') (cmd.read command)`
lines : Cmd -> IO (Array String)
# 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)`
eachline_ : Cmd -> (String -> IO ()) -> IO ()
show : Cmd -> StringType Classes
Equality
class Eq a where
eq : a -> a -> Bool
neq : Eq a => a -> a -> Boolinstance Eq Bool
instance Eq Char
instance Eq String
instance Eq Int
instance Eq a => Eq (Array a)Comparison
class Eq a => Ord a where
compare : a -> a -> (| Less : (), Equal : (), Greater : () |)
lte : a -> a -> Bool
lt : a -> a -> Bool
gte : a -> a -> Bool
gt : a -> a -> Boolinstance Ord Bool
instance Ord Char
instance Ord String
instance Ord Int
instance Ord a => Ord (Array a)Debugging
class Debug a where
debug : a -> String
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 : DebugRecordFields a => { a } -> Array { field : String, value : String }
debugVariantCtor : DebugVariantCtor a => (| a |) -> { ctor : String, value : String }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
# ```
program : IO String
# 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"]
# ```
args : IO (Array String)
getvar : String -> IO (| Some : String, None : () |)
getvar! : String -> IO String
setvar : String -> String -> IO ()exit
module exit where
success : IO a
failure : IO a
with : Int -> IO afile
module file where
read : String -> IO String
write : String -> String -> IO ()
append : String -> String -> IO ()path
module path where
exists : String -> IO BoolGrammar
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