General Unison FAQ's

Language

Does Unison have Haskell-style type classes?

No, or at least, not yet. We're considering our options for how to allow people to express bounded polymorphism. See this post for some initial observations.

Currently there are two ideas which have been developed at least to some extent.

  1. Like this: mconcat : [a] ->{Monoid a} a, where Monoid a is an ability.
  2. Like this: mconcat : Monoid a -> [a] -> a, where Monoid a is a record type.

Both of these would require a set of supporting language enhancements, for example to supply the Monoid a handlers/values automatically.

How similar is Unison to Haskell?

As a programming language, Unison can be viewed as a descendant of Haskell, with many similarities including

  • type inference
  • pattern matching
  • ADTs
  • purity (i.e. no side effects).

Some key differences...

  • As a new language, Unison is a good deal smaller and simpler than Haskell. The flip side of this is that it's currently missing a number of conveniences which Haskell users take for granted.
  • Unison supports algebraic effects, which it calls abilities, which replace monads and monad transformers for writing effectful code.
  • Unison uses a strict evaluation model, whereas Haskell's model is lazy.
  • The do keyword in Unison starts a codeblock as a delayed computation, it does not match Haskell's "do notation" for any monads.
  • The let keyword in Unison starts a codeblock. It is not the start of a variable definition.

🌻 While there are similarities between the two languages, it is not an expectation that people know Haskell in order to program in Unison.

Does Unison have an FFI?

Unison does not currently support a Foreign Function Interface, for invoking code written in other languages.

Your programs can interact with the outside world via the IO ability, and this includes interaction via network sockets - so you can interact with code written in other languages if that code can expose a network interface, for example as a web service. We'd like to improve on this position in the future.

The sketch design for how it will work is as follows.

  • A foreign API, let's say one for working with the GPU, will be exposed through a top-level ability, let's say GPU.
  • The Unison runtime will let you run a program of type '{GPU} () just as it lets you run a '{IO} ().
  • There will be an extensible binding mechanism for defining new such abilities and forwarding their operations to external APIs.

There's still plenty to work out, for example in

  • designing the binding mechanism
  • managing ucm linkage dependencies to external binaries
  • making it play nicely with distribution, so computations can jump between nodes, and make use of the varying FFI abilities on each one.

Note how using the ability mechanism gives a great story for testing. You can define a test handler, in pure Unison, to handle your GPU ability, and use it to add regular Unison tests for your GPU code, that can run anywhere regardless of whether the right GPU is installed.

Distributed computing

Is there a way to run Unison on my own cluster? How do I develop and test Unison distributed programs locally?

Currently we don't have an easy way for folks to run and manage a distributed Unison program on their own cloud computing cluster but you can run the Cloud on your own computer locally and the Unison Cloud will also offer a free tier for users. The Unison team is hard at work on the Unison Cloud Platform which provides cloud computing as a service. Local simulation of the Cloud and Remote handlers is supported via the cloud client and the distributed library. Our interface for distributed programming is independent from the hardware that runs it, so familiarizing yourself with the API and writing programs is something you can do today.

Sign up for beta access to the Cloud here. Unison Cloud is our platform as a service product and we hope its success sustainably funds the open source development of the Unison language, tooling, and ecosystem as a whole.

How can Unison's distributed execution be made secure?

There are actually a bunch of questions here - some are listed below. Some aspects of this need more research, but overall we believe that it will be possible to build secure systems using Unison.

How can a Unison node ensure that a peer which sends it a computation (a) is who it says it is, (b) is authorized to consume compute resources on the node, (c) is authorized to access data or perform effects on the node?

Computations from remote notes will be sandboxed, with resource and capability permissions applied to the sandbox. There will be a system for authenticating remote nodes and authorizing them for a given usage.

How can a Unison node know that code which it syncs in from a peer is safe to run, and will only use resources or privileges that the sender is authorized for?

Resource usage will be limited dynamically through the sandbox mechanism, with code execution throttled or cancelled if it breaches the limits. A program will only be able to use effects and I/O if the sandbox explicitly provides it the ability to do so. Unison's type system enforces that programs only have access to the abilities they're given.

How can a piece of Unison code/data be kept private to a node - so it doesn't get synced to other nodes?

For data, there is a plan to add a "pinned data" facility, see issue #798.

For code, this is an active topic of research - see issue #799.

Given that Unison transfers code and data to peers, are any special code patterns required in Unison to prevent vulnerabilities?

Certainly yes, and it will be fun to build out our understanding of these patterns as we gain experience with the distributed execution API!

What does each node need to run to enable distributed execution?

A Unison runtime environment must be running on each node. This environment will accept network connections from Unison peers, and run the code syncing and distributed evaluation protocol.

Details on how this environment looks are TBD.

Where can I learn more about Unison's support for distributed computation?

This area of Unison has had plenty of thought and research and is in active development. Here are some resources to give you a preview.

Build/install

Is there a nix expression for Unison?

Check out the unison-nix repository.

Nix is not officially supported, but there seems to be a substantial overlap between nix enthusiasts and Unison enthusiasts, so feel free to give it a try and ask on slack if you get stuck.

I get error while loading shared libraries: libtinfo.so.5

Some people see the following error when they try to run the ucm executable for the first time.

ucm: error while loading shared libraries: libtinfo.so.5: cannot
open shared object file: No such file or directory

As a workaround try the following:

-- assumes APT is your package manager
sudo apt-get install libtinfo5

This issue is tracked under #602.

When I save my scratch file, ucm doesn't react

On Linux, this can happen if your system has run out of inotify watches. (Backup applications often use a lot of these.)

You can see if this is the case by running tail -f <file> on a file of your choice, and looking out for the following output.

tail: inotify cannot be used, reverting to polling: Too many open files

If so you can up the limit by doing

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

Search stackoverflow for more tips or get in touch with us in the community slack if that command doesn't work for you.

Codebase model

If definitions never change, how do you fix a bug or refactor an old definition?

You can also check out Paul's Scale By The Bay 2019 talk on the Unison codebase model for an explanation!

Definitions never change, but the names we give them do. Suppose I have a term with hash #1krif5, and I've given it the name foo. We can draw that as follows.

foo   ->   #1krif5

Then I decide my code for foo is bugged, so I present Unison with a new definition for it. Then Unison will work out the hash of the new definition for foo, say #amrl61, and change the name foo to point to it. We end up with the following.

#1krif5

  foo   ->   #amrl61

So, no terms have changed**, but a new term has been added, and a**name has changed to point to a different term.

Propagation

So that's all good, but what about the function bar which called foo?

Suppose we actually originally started with the following - one term referencing (calling out to) another, those terms being called bar and foo respectively.

foo   ->   #1krif5
                ↑
  bar   ->   #doq7s1

Then our edit to foo actually left the codebase (for a moment) in the following situation, with bar still referencing the old version of foo.

#1krif5
               ↑
  bar   ->   #doq7s1

  foo   ->   #amrl61

However, Unison assumes that this is not where you want to end up, and that actually, you want to propagate the updated definition of foo to the set of transitive dependents of the old term #1krif5.

As long as the old foo and the new foo have the same (or compatible) types, then Unison does this propagation automatically and leaves you with the following.

(unused)   #1krif5

  bar   ->   #doq7s1
                ↓
  foo   ->   #amrl61

This propagation works recursively, so it deals with dependents of bar as well, and their dependents in turn, and so on.

If the types aren't compatible, then Unison can't automatically propagate the change. Let's suppose the old definition of bar had type Int, the new one has type Nat, and bar requires foo to be an Int. Then the todo command in ucm reports the following:

.> todo

  🚧

  The namespace has 1 transitive dependent(s) left to upgrade. Your edit frontier is the dependents
  of these definitions:

    foo#1krif5 : .base.Int

  I recommend working on them in the following order:

    bar : .base.Int

This is your cue to resolve the type discrepancy by editing bar (or rather, switching the name bar to refer to some new definition) so that it copes with foo being a Nat. If that edit to bar preserves its type, then Unison can take it from there and continue the propagation process automatically.

Notice that even while we were in the transient state as reported by todo, our codebase was still valid, and all our tests would still pass - it's just that at least some of them would still refer to the old definitions of foo and bar. And at no stage did we have to wade through a mass of compile errors: instead, the todo command leads us through a structured refactoring process, in which everything is valid and well-typed at all times.

Another thing: the instruction to 'replace #1krif5 with #amrl61 gets recorded by Unison in a patch. Suppose you publish foo in a library for other people to use. Then your library users can take advantage of your patch to help them update their own code.

See our document about resolving conflicts for more information.

What happens if I hit a hash collision?

Your name will go down in history!

Unison uses 512-bit SHA3 digests to hash terms and types. The chance of two Unison objects hashing to the same digest is unimaginably small. If we wrote one billion unique Unison terms every second, we could expect a hash collision roughly every 100 trillion years.

If it did happen, you could simply tweak your term so it gets a different hash. For example, you could wrap it in a call to the identity function (which does nothing), or add a document literal to the term like {{wow this is unlikely!}}

Is it inefficient to have the codebase be a purely functional data structure? Won't that involve a lot of copying?

Since the codebase is an immutable data structure where the elements of the structure never change, we can get the same kind of structural sharing we’re used to getting with data structures in functional programming.

In fact, Unison may waste a lot less disk space than traditional languages, since Unison does not have builds and therefore doesn’t generate build artifacts.

Does the codebase always keep getting bigger over time? Is there any way to remove code?

The codebase stores its complete history - the same as git does. That gives you a complete view of the operations that brought the codebase to its current state. It also means that you can refer to a given definition in the codebase, by hash, and be sure that that reference will never become undefined.

In the future, we may introduce a "prune" operation to allow you to remove definitions which have no inward references (from within the codebase).

How do I share my code?

If you want to share your code (or look at code that others have shared), check out Unison Share.

You use the Unison Codebase Manager's push command, to write it to your remotely hosted repository.

(There's also always the option of zipping up your .unison directory! Its contents are free-standing and portable.)

How does hashing work for mutually recursive definitions?

We treat a set of mutually recursive definitions as a single "cycle" for hashing purposes.

For example:

f x = g (x - 1)
g x = f (x / 2)

First we replace any recursive calls in the cycle with the De Bruijn index of the cycle itself, like this (not valid Unison syntax):

$0 =
  f x = $0 (x - 1)
  g x = $0 (x / 2)

Nested cycles will get higher De Bruijn indices, but a top-level cycle will have index 0.

This transformation makes the elements of the cycle independent of any names. Then we hash each element of this new structure individually. Let's say f gets the hash #222 and g get the hash #111. We then sort them to get a canonical order that is independent of their order in the source. This yields something like:

$0 =
  $0.0 x = #111
  $0.1 x = #222

We then hash this structure. Let's say that hash is #ccc. Each definition gets a hash #ccc.n where n is the position of that definition in the cycle. Here g gets #ccc.0 and f gets #ccc.1. The final cycle will be:

#ccc =
  $0 x = $1 (x / 2)
  $1 x = $0 (x - 1)

This creates two new hashes, #ccc.0 and #ccc.1. Note that their definitions don't refer to each other by hash, but by position in the overall cycle.

General

How do I run a program that uses IO?

In ucm, you can type run myProgram`, where myProgram'' is some term of type '{IO, Exception} ().

Check out this document about the ways to run Unison programs

Does Unison have IDE support? Editor support? Language Server Protocol (LSP) support?

Unison has an LSP integration. Here's how to set it up.

We have editor support for the Unison syntax in Vim, Atom, and VS Code - see our document about setting up your editor.

No editors currently understand the Unison codebase format, or offer functionality like "find references", "extract function", etc. You can use ucm commands to get some of these abilities, for example find.

Unison ships with a local UI to browse your codebase. Libraries can be explored and published on Unison Share

Since the Unison codebase is a structured object containing full type information and metadata, we expect it will be possible to build developer tooling which beats that which exists for today's languages!

A previous incarnation of the Unison project actually started with a structural editor, meaning that the "input some freeform text" stage is bypassed entirely. This is a direction we hope to come back to in the future.

Does Unison compile to LLVM?

Not yet! But a native compilation is coming to for Unison soon. For the moment, we have a nice, clean, but un-optimized interpreter in Haskell.

Are you looking for help with developing Unison?

Yes! Please come and get involved 😊

The first step is to play with the language, and get familiar with writing Unison code. Also come join the Discord, and browse through the issue tracker to see what's going on.

Contributing Unison code

You can contribute to the Unison ecosystem directly by cloning a library from Unison Share and creating a branch with your feature or change. The Unison library author will be notified of your changes and can merge them into their library. (Be sure to ping them on Discord in the #libraries channel as a heads up!)

Is there a library you could write in Unison? That's a way to contribute which requires very little coordination with the compiler team, and can have a big impact on the usability of the overall Unison ecosystem. There's a catalog of libraries on the front page of Unison Share - do add yours! Let us know what you're working on in the Discord (channel #libraries). 😎

You could also try fixing some omissions from the base library. See here for the contributions that would be most welcome.

Contributing to the Unison language/compiler/toolchain

You can dip your toes by finding small ways to contribute a little piece:

  • take a look at the issues labelled good first issue and help wanted
  • see if you can find any tidying or refactoring you think needs doing in the parser-typechecker source (chat to us on slack before spending much time)
  • are there any contributor docs you'd like to see which you could make a start on?

Once you've had your first PR merged and gotten to know how we work, have a think whether you want to take on a bigger project! There's lots of cool stuff to do. Get in touch with Paul Chiusano via the Discord and give him an idea of your areas of interest and level of experience. If you have your own suggestions for what you could work on then let him know!