news
May 26, 2020

People are writing Unison libraries now

Rúnar Bjarnason

Last week we released another Unison alpha milestone. This release has anumber of bugfixes and performance improvements.🌈⭐️

It's exciting to see Unison gradually coming together, but even more exciting has been seeing people writing and publishing Unison code. I've been pretty busy reviewing pull requests to theUnison base libraries,and there's already a lot of useful stuff there. Thanks to everyone who has contributed so far!

A number of folks have been working on their own Unison libraries and we're collecting theseon a page of this site.I want to highlight just one of these, which is a random number generator. There are many other cool libraries, but I picked this one at random. The main thing I want to highlight is just how easy it is to create and use Unison libraries.

Starting up

If you've been putting off trying out Unison because you think getting into a new language might be a lot of work and involve a lot of setup, then I've got good news for you: Unison requires almost no setup. You can just start coding.

To get going with Unison, you can go through thethree minute quickstart guide.It only has three steps.

Once set up,ucmstarts the Unison Codebase Manager:

Unison Codebase Manager

I can now just start writing Unison code in any file in the directory from which I starteducm,as long as that file's name ends in.u.I open up a filescratch.uwithvim(my favorite editor), and write my first definition, the obligatoryfactorialfunction:

factorial n = List.foldr (*) 1 (range 2 (n + 1))

If I save my scratch file, Unison responds with:

ucm output: factorial

It's telling me thatfactorialis a function that takes aNat(a 64-bit unsigned integer), and returns anotherNat.I can add this function to my codebase:

Adding factorial to codebase

I can try out my function right in the scratch file by adding a "watch expression", which is any line that starts with>(or an indentation block where the first line starts with>).

> factorial 10

Unison reponds by evaluating my expression:

watch expreission

Looking around the base library

Thebaselibrary is already full of good stuff, with new things being added almost daily now. To see what's here, I can browse with UCM. If I dolist .base(orls .base),I get a long list. I cancdinto any namespace and look around withls.

In the definition offactorialabove, I was usingfoldrandrange.If I ask UCM tofind range,it responds with everything it knows about withrangein the name:

find range

The second entry is the function I was using,List.range.I can ask for its definition either by name or by using the number from the list that UCM produced:

view range

Thedocscommand (tryhelp docs)shows me the documentation for a given function or type, if it has any docs associated with it.

docs

Let's have a look at a third-party library

Looking at thelist of libraries that people have already made,I want to pick one, kind of at random, and just play with it to give you a feel for what it's like to consume libraries in Unison. And speaking of random, it looks like one of them isa random number generator that uses a Mersenne twister,written by Chris Gibbs. Let's try that out.

Chris has supplied handy install instructions, so let's just do as he says:

.> pull https://github.com/atacratic/unison-random-mersenne.git:.releases._v1 external.unison_random_mersenne.v1

Lots of stuff scrolls by, and Chris's library is now in my codebase. The namespaceexternal.unison_random_mersenne.v1is kind of long for my taste, so I'm just going to move it underlib.rng.

.> move.namespace external.unison_random_mersenne.v1 lib.rng

I'm using tab completion for this instead of typing out the whole thing. Icd lib.rngand look around.

cd lib.rng

Looks like there's aREADME,which is a Unison value of typeDoc.I can ask UCM to render anyDocwith thedisplaycommand. In theREADMEthere's a usage example which shows me how to generate some random numbers.

I put a triple dash---,a "fold", on a line at the top of myscratch.ufile, which tells Unison to ignore everything below. I've already addedfactorialto my codebase, so I don't need to think about that anymore. It goes below the fold. Above the fold, I put an expression to generate a single random number between 0 and 10 which I got from the README.

> mersenne.provide (nextFromRange 0 11) defaultSeed 'ask
---
factorial n = List.foldr (*) 1 (range 2 (n + 1))

I wonder what thismersenne.provideis. Let's ask Unison.

mersenne.provide

OK, soprovidegenerates numbers by providing aStore Stateability, whereStateis the Mersenne twister's internal state. TheNat32is the seed, and thisAsk nthing is a computation that can ask for a randomn(which in my case is going to beNat).

Looks like this library puts a lot of its guts on display, so I'm going to try to write a more abstract interface for it. What I really want for a random number generator ability is something like this:

ability RNG where
  nextNat : Nat

I can write a (recursive) handler for this ability that uses the Mersenne twister:

mersenneRNG : Nat32 -> '{RNG,e} a ->{e} a
mersenneRNG seed x =
  h = cases
    { nextNat -> k } -> handle (k !nat.next) with h
    { a } -> a
  mersenne.provide '(handle !x with h) seed 'ask

Great! So now I can request a randomNatjust by sayingnextNat,and I can handle that request with `mersenneRNG`by providing a seed. But sometimes I don't want to provide a seed, and I just want to generate a random number as anIOeffect. Let's try to feed it the system time.

system type

OK, we can get anEpochTime,which wraps aNat,usingsystemTime.Can we turn that into aNat32as required bymersenneRNG?A type-basedfindturns up 3 different functions:

type based find

Skimming thedocsfor these, I see thattruncate32is probably the thing I want. It truncates the high 32 bits, leaving me with just the least significant bits of the clock. So I write:

clockSeed : '{IO} Nat32
clockSeed _ =
  match !systemTime with
    EpochTime t -> truncate32 t

And finally I use themersenneRNGhandler from earlier:

mersenneIO : '{RNG,e} a ->{IO,e} a
mersenneIO x =
  mersenneRNG !clockSeed x

An example of using this in a program might be:

main : '{IO} ()
main = 'let
  printLine (Nat.toText (mersenneIO 'nextNat))

Iaddall of that to ascratchnamespace in my codebase:

add to scratch namespace

Let's try to run it. UCM provides aruncommand which provide theIOability to something of a type like'{IO} a.

run

Great! I got a random number using the system clock time as a seed. Maybe at some point I'llsend a pull requestto Chris so he can incorporate some of this into his library.

Conclusion

I hope this gives you a taste of what it's like to work in Unison with the current alpha release, and that this encourages you to try it out and maybe contribute some libraries. Remember, it's early days yet, so little things you build now could make a big impact on the Unison community for the future.