FAQ's for abilities

Can I define an ability which references other abilities?

You cannot, at present, define an ability that directly specifies another ability requirement in its request constructors. Let's say I wanted to write a Cache ability. I might want to express something like:

-- 🙅🏻‍♀️ you can't do this
ability Cache k v where
  get : k -> {Cache k v, IO} v
  put : k -> v -> {Cache k v, IO} ()

After all, I know it's likely the users of my service will want to do IO as a part of handling their get and put requests. However, we can't specify that in the ability definition. The preferred way to allow for a Cache ability to perform IO and exceptions would be to write a handler that interprets theCacheability into theIOability.

What doesRequestmean?

Theabilities.Requesttype is the type that Unison uses to model therequest constructorsof an ability.

In the expressionhandle !myEffect with myEffectHandlerthe termmyEffectHandlerwould have a type which lookssomethinglikeRequest (MyEffect) a -> a- The exact semantics are described in thelanguage guide section on handlers.

When defining your own handlers you might see theabilities.Requesttype, but callers of handler functions shouldn't typically need to manage it directly.

Why can't an ability be at the "top-level" of a value?

For example, you can't do:myTerm = printLine "Hi".

Currently values at the "top level" of a program have to be pure, so it's more common to seedelayed computationsthat contain abilities (expressed with the single quote syntax). This may change in a future version of Unison. Think of the ability constraint as a constraint onthe function arrowas opposed to a constraint on a value.

How do I test something which requires the IO ability?

Run a single test which performs IO with theio.test command.Theio.testcommand expects adelayed computationwithIOas an ability requirement. The rest of the Unisontesting conventionsremain unchanged.

You can also test functions which perform IO with Unison'stranscriptsby interleaving Unison code which performs IO with UCM fenced codeblocks that call theruncommand.

A document about writing Unison transcriptscan be found here.

How do you express failures when writing a custom ability?

For example, if you write aHTTPability or aCacheability, you may want to capture the fact that a request may fail.

Here are a few strategies that you'll see employed:

  • Rather than representing the failure in the definition of the ability itself, the handlers of the ability can account for failure, calling other abilities that represent a failure state, or by using data types that represent the failure.
  • You might use a data type likeOptional,orEitherin the type signatures of your ability's operations themselves.
  • Some abilities contain a specific request constructor that represents failure.For example, the Tokenizer ability on shareThis design decision is less common. A heuristic to use when deciding whether to build a "fail" operation into your ability is if the "fail" operation is one of the key behaviors of the effect you're trying to model, as opposed to an operational hazard.

Why does a handler need a case which doesn't refer to the ability operations?

Handlers often a case in their pattern match which takes the form{r} -> ....

This case in the handler needs to be present for those situations where an ability might berequiredby a function but is not ultimately called or where the function calls the ability but returns a regular value as the result of running the last expression. For example, this function won't always need to callabort:

errorHandling.divBy : Nat -> Nat ->{Abort} Nat
errorHandling.divBy a b =
  match b with
    0 -> abort
    n ->
      use Nat /
      a / b

A handler for this function might look like:

Abort.toOptional! : '{g, Abort} a ->{g} Optional a Abort.toOptional! f = handle !f with cases { r } -> Some r {abort -> resume} -> None Abort.toOptional! '(errorHandling.divBy 4 2)

Without the{r} -> ...case, calling an expression which doesn't invokeabortwill return an error.

💔💥

I've encountered a pattern match failure in function
`toOptional!` while scrutinizing:

  !'(divBy 4 2)

What's the relationship between abilities and monads?

That deserves a longer discussion. The short answer is that abilities are as expressive as monads and are equi

But for nowcheck out this gist!

I see some code which leaves off the ability requirements in an ability declaration, what gives?

When defining an ability's operations, it's often a nice shorthand to leave off the implied ability requirement—that is, the{Abort}inabort : {Abort} a—afterall, we know that an ability operation will be performing the ability in question and will require a handler. Unison will fill that in for us if we omit it, so we could have also defined the type signature of the abort operation asAbort.abort : x.

🚧🚧 🏗 More coming soon 🚧🚧