πŸ‘€ Unison at a glance

This document contains code snippets with a minimum of exposition. Links are provided throughout to more comprehensive docs sections. If you haven't downloaded the UCM,you might want to do that first. 😎

Hello World

Write your Unison code in any.usuffixed"scratch" file.

The classichelloWorldprogram performs console interactions via the IO ability.Read about how abilities model effects in Unison.

helloWorld : '{IO, Exception} ()
helloWorld = do
  use Text ++
  name = !readLine
  printLine ("Hello " ++ name)

Execute the entry point to your program with therunUCM command.

.> run helloWorld

OraddyourhelloWorldprogram to the codebase and run it by packaging it up in a binary executable!

.> add
.> compile helloWorld helloFile

This will produce an executable file calledhelloFile.ucin the same directory as the codebase. You can then start your program in your terminal.

$ ucm run.compiled helloFile.uc

πŸ“šLearn more about executing Unison programs

Basic functions

The following introduces a functiondoublewith one parameter.Unison conventions for defining functions are detailed here

glance.double : Nat -> Nat
glance.double x =
  use Nat *
  x * 2
> double 4

The>in a scratch file runs thedoublefunction in awatch expression.

πŸ“šThe Unison tour walks through watch expressions and other workflow features

Delayed computation syntax

The'symbol is syntactic sugar for introducing athunkand the!symbol is syntactic sugar for calling the thunk.

greet : Text -> Text
greet name =
  use Text ++
  "Hi " ++ name
delayGreet : 'Text
delayGreet = '(greet "Joy")
!delayGreet
⧨
"Hi Joy"

A more detailed look at delayed computations

List literals

Square brackets introduce aUnison list.

[0, 1, 2, 3] List.++ [4, 5]
⧨
[0, 1, 2, 3, 4, 5]

TheList.++is ouroperatorfor list concatenation.

head = use List +: 1 +: [2, 3, 4] head :+ 5
⧨
[1, 2, 3, 4, 5]

A variety of list patterns are available for lists.

πŸ“šLearn more about common collection types in Unison

List transformations

Nat.range 0 10 |> List.map (x -> x Nat.* 100) |> List.filter (const true) |> List.foldLeft (Nat.+) 0
⧨
4500

The|>operator is a "pipe" which passes the result of executing the expression on the left as an argument to function on right.

The parenthesizedx -> x Nat.* 100argument toList.mapis an example oflambdas in Unison.

πŸ“šLearn more about operators like|>

if/else and pattern matching

The expression below is written with bothif then and else syntaxand withpattern matching syntax

use Nat mod
isEven1 num =
  use Nat ==
  if mod num 2 == 0 then true else false
isEven2 = cases
  n | mod n 2 === 0 -> true
  _ -> false

Unison'spattern matching featuresinclude variable binding, pattern guards (separated by|),and as-patterns (indicated with an@).

match Some 12 with None -> "none" Some n| Nat.isEven n -> "n is a variable and | is a pattern guard" opt@(Some n) -> "opt binds to the entire optional value"
⧨
"n is a variable and | is a pattern guard"

Type declarations

A unison data type with uniqueness determined by its name:

unique type LivingThings
unique type LivingThings = Animal | Plant | Fungi | Protists | Monera

A recursive Tree data type with a single type parameter:

structural type glance.Tree a
structural type glance.Tree a
  = Empty
  | Node a (glance.Tree a) (glance.Tree a)

The structural keyword means that types defined with the same structure are identical.

More on data types and the difference between structural and unique.

Record types allow you to name the fields of your type.

unique type Pet = {
   age : Nat,
   species : Text,
   foodPreferences : [Text]
}

Creating a record type generates a series of helper methods to access and update the fields of the data type.

.> add Pet

Record type syntax in depth

Exception handling

nonZero : Nat ->{Exception} Nat
nonZero = cases
  n
    | n Nat.== 0  ->
      Exception.raise (Generic.failure "Zero was found" n)
    | otherwise   -> n
catch '(nonZero 0)
⧨
Left (Failure (typeLink Generic) "Zero was found" (Any 0))

An exception is "raised" with theExceptionability and "caught" with a handler.

Our error handling with abilities doc describes this pattern and more error types in detail.

Using abilities

Abilities are used for effect management in Unison.

getRandomElem : [a] ->{Random} a
getRandomElem : [a] ->{Random} a
getRandomElem list =
  index = Random.natIn 0 (List.size list)
  List.unsafeAt index list
splitmix 42 '(getRandomElem [1, 2, 3, 4, 5])
⧨
5

This plucks a random element from the list withRandom.natIn,a function using theRandom ability.

splitmixis an example of anability handler.

Distributed computations

Distributed computations can be expressedin the Unison language itselfthrough theRemoteability.Read about the Remote ability and its features

distributed : Seq k Nat ->{Remote} Nat
distributed dseq =
  use Nat + ==
  dseq
    |> Seq.map (x -> x + 1)
    |> Seq.filter (x -> Nat.mod x 7 == 0)
    |> Seq.reduce 0 (+)

This simple map/reduce code can operate over a distributed sequence, where the data may live in many different nodes in a cluster.This distributed computation use case has been fleshed out in an article.

Issuing an http request

Pull the library fromUnison Share.with thepullcommand.

.> pull stew.public.projects.httpclient.latest lib.httpclient
exampleGet : '{IO, Exception} Response
exampleGet _ =
  unisonWeb = Authority None (HostName "share.unison-lang.org") None
  path =
    use Path /
    Path.root / "users" / "unison"
  uri = Uri https (Some unisonWeb) path Query.empty None
  req = Request.get uri
  handle request req with Http.handler

The first part of this code uses data constructors from the http library to create a full uri out of an authority and path. The request is handled by passing it to the Http handler.

The http library has great docs!

Basic file operations

glance.readFile : '{IO, Exception} ()
glance.readFile _ =
  filePath = FilePath "tmp.txt"
  read : Handle ->{IO, Exception} Bytes
  read fileHandle =
    go acc =
      use Bytes ++
      use Nat <
      bs = getBytes fileHandle 4096
      if Bytes.size bs < 4096 then acc ++ bs else go (acc ++ bs)
    go Bytes.empty
  fileHandle : '{IO, Exception} Handle
  fileHandle _ = open filePath Read
  bracket
    fileHandle
    Handle.close
    (file -> read file |> fromUtf8 |> printLine)

File operations are accomplished by setting theFileModeand manipulating a fileHandle.Seeking through the file can be done via thegetBytesfunction, which we've opted to perform in arecursive function.

These and other file functions are located in thebase.ionamespace.

.> find file

Concurrency primitives

Concurrency primitives likeMVar,TVar,andSTMare built into the base library.TVarandSTMmake it easy to write lock-free concurrent mutable data structures. For instance, here’s a simple lock-free queue implementation and a few helper functions:

unique type TQueue a
unique type TQueue a = TQueue (TVar [a]) (TVar Nat)
enqueue : a -> TQueue a ->{STM} ()
enqueue : a -> TQueue a ->{STM} ()
enqueue a = cases
  TQueue elems _ ->
    use List +:
    TVar.modify elems (es -> a +: es)
dequeue : TQueue a ->{STM} a
dequeue : TQueue a ->{STM} a
dequeue tq =
  match tryDequeue tq with
    None   -> !retry
    Some a -> a

The block introduced bySTM.atomicallybelow ensures that no one can access state of the queue until after the actions in the block have taken place.

queueExample : '{IO, Exception} ()
queueExample _ =
  runQueue : '{STM} Nat
  runQueue _ =
    queue = TQueue.fromList [1, 2, 3, 4, 5]
    enqueue 6 queue
    dequeue queue
    dequeue queue
  result = STM.atomically runQueue
  printLine (Nat.toText result)