Exercises using abilities

If you need some inspiration for using and writing abilities, we've curated a list of exercises fromthe Unison language Exercism trackthat either benefit from the one or more of the abilities found inbase,or that include stubs for writing your own ability handlers. The descriptions below make note of how the problem might benefit from using abilities, while full instructions, tests, and stub files can be found by following the links in each section!


๐Ÿ““Majority element

The majority element is one which takes up over half of the space in a collection. Given a list of elements representing the colors, return the color found as the majority, otherwise indicate that no majority has been found.

While you can implement the function under test in any way you choose, we've provided a few stubs which use theStoreability. For additional practicewritinghandlers, we've given a handler stub calledrunWithto implement.runWithreturns both the result of running the function which uses the Store ability and the final state of the Store.

๐ŸŒŸSee the full exercise


๐Ÿ““Stream operations

This exercise is about implementing basic Stream ability operations. Writing your own handlers for common functional combinators likemap,flatMapandfilter.

๐Ÿ“šAdditional challenge, incorporate theAskability inStream.pipe
๐Ÿ“šAdditional challenge, incorporate theAskability inStream.pipe
structural ability Ask a
structural ability Ask a where lib.base.abilities.Ask.ask : {Ask a} a

Here's another example ability,Ask,which provides one operation,ask,for requesting a value of typeafrom whatever code handles the ability.

provideis a handler for ask which provides a constant value.

A common usage ofAskis to avoid needing to pass around common configuration settings, justprovide myConfig 'myMainto make myConfig available anywhere inmyMainwith a call toask.

provide 10 '(1 Nat.+ ask Nat.+ ask)
โงจ
21

Computations that useAskcan also be thought of as stream consumers. Try writing a functionpipe,which can be used to statefully transform a stream:

abilityExercises.Stream.pipe : '{Stream a} ()
                               -> '{Ask a, Stream b} r
                               -> '{Stream b} ()
abilityExercises.Stream.pipe :
  '{Stream a} () -> '{Ask a, Stream b} r -> '{Stream b} ()
abilityExercises.Stream.pipe = todo "implement me"

In implementing this, you'll have a handler that matches on aRequest {Ask a, Stream b} r.Handlers that match on multiple abilities at once like this are sometimes called "multihandlers." There's nothing special you need to do in Unison to write multihandlers; just match on the operations from more than one ability in your handler!

Once you've writtenabilityExercises.Stream.pipe,try writingStream.map,Stream.filter,andStream.takeusingabilityExercises.Stream.pipe.

๐ŸŒŸSee the full exercise


๐Ÿ““Matching brackets

Given a string containing brackets[],braces{},parentheses(),or any combination thereof, verify that any and all pairs are matched and nested correctly.

There are many ways to accomplish this exercise, but we've provided some additional stubs for folks who'd like to practice writing their own ability handlers.

The signature for thecheckBalancehelper function given for this exercise makes use of an ability requirement,{Stack}.The Stack ability is defined for you, but the handler,Stack.runis not! If you chose, you can implement the Stack.run, checkBalance, and isPaired function. The tests will run even if you choose not to implement this exercise with abilities.

๐ŸŒŸSee the full exercise


๐Ÿ““Change

Correctly determine the fewest number of coins to be given to a customer such that the sum of the coins' value would equal the correct amount of change.

This exercise doesn't explicitly require abilities, but many folks have used theStoreability for memoization when optimizing their solution.

๐ŸŒŸSee the full exercise


๐Ÿ““Zipper

Write a Zipper that allows you to navigate through a binary tree data structure.

There are many ways to accomplish this exercise, but we've tailored this exercise for folks who'd like to practice writing their own Zipper ability.

The Zipper ability itself is defined for you, but you'll need to implement the handler that allows it to navigate through the given binary tree data structure.

๐ŸŒŸSee the full exercise


๐Ÿ““Zebra Puzzle

Given a set of possible attributes of a number of houses and a series of constraints, find the house that whose resident owns a zebra.

This problem offers an opportunity to use theEachability for non-determinism in thebaselibrary.

You might also want choose to use an ability for optimizing the search space when finding who owns the zebra. ๐Ÿค”

๐ŸŒŸSee the full exercise

More exercises using abilities

๐Ÿ““Implement functions with calls to Ability operations

Given the type signatures below, implement the functions to satisfy the compiler using the request operations of the Ability or functions found in.base

getWithAbort : a -> Map a b ->{Abort} b
getWithAbort key map = base.todo "implement me!"
โœจHint: how do I see the request operations of an ability?
โœจHint: how do I see the request operations of an ability?
In the UCM, you can enterview Abortto see the ability definition. The function signatures after thewherekeyword are the request operations.
๐Ÿ”‘Answer:
๐Ÿ”‘Answer:

Using theOptional.toAbortfunction.

getWithAbort : a -> Map a b ->{Abort} b
getWithAbort key map = toAbort (get key map)

Pattern matching onOptional

getWithAbort : a -> Map a b ->{Abort} b
getWithAbort key map =
    match get key map with
      Some a -> a
      None   -> abort

๐Ÿ““Implement functions with Ability operations

rangeAverage : Nat ->{Store [Nat]} Float
rangeAverage n = base.todo "implement me"

Using theStoreability to keep track of aListof numbers, write a function which takes in some numbernofNatas an upper bound of a list from0up to (but not including)nand returns the average of the final list.

So, given an upper value of5,the average produced would be the average of[0, 1, 2, 3, 4],or2.0

๐Ÿ”‘Answer
๐Ÿ”‘Answer

One possible solution is as follows:

averageOfRange : Nat ->{Store [Nat]} Float
averageOfRange n =
  use Float / fromNat
  use List +:
  use Nat + ==
  go = cases
    i
      | i == n     ->
        myList = Store.get
        listSize = List.size myList
        sum =
          lib.base.data.List.foldLeft
            (acc element -> element + acc) 0 myList
        fromNat sum / fromNat listSize
      | otherwise  ->
        Store.modify (currentList -> i +: currentList)
        go (i + 1)
  go 0

๐Ÿ““Apply a handler to a function requiring abilities

Check out the functions in thebaselibraries for operating onStoreand see if you can apply a handler to the function you wrote previously forfinding the average of a range.The handler should help you return theFloatvalue to check your function.

โœจHint: What kind of function am I looking for?
โœจHint: What kind of function am I looking for?
You'll want a function whose return type does not requireStorein curly braces in its final value. The handler should run or "interpret" the program which performs aStoreability into another value.
๐Ÿ”‘Answer:
๐Ÿ”‘Answer:

One possible solution is as follows:

handleAverageOfRange : Float
handleAverageOfRange = withInitialValue [] '(averageOfRange 5)

withInitialValuehandles a function that requires theStoreability by setting the initial state of theStore.


๐Ÿ““Debug an abilities issue

The following watch expression is failing to typecheck.

randomIndex : [Nat] -> '{Random, Abort} Nat
randomIndex list = 'let
  n = List.size list
  if n === 0 then abort else Random.natIn 0 n

> toOptional! (Random.lcg 7 (randomIndex [1,2,3]))

The UCM error is:

The 2nd argument to `(<|)`

            has type:  Nat
      but I expected:  Unit ->{g, Abort} ๐•ฉ

    142 | randomIndex : [Nat] -> '{Random, Abort} Nat
    143 | randomIndex list = 'let
    144 |   n = List.size list
    145 |   if n === 0 then abort else Random.natIn 0 n
    146 |
    147 | > toOptional! (Random.lcg 7 (randomIndex [1,2,3]))

Why is the error occurring and how might it be corrected?

๐Ÿ”‘Answer:
๐Ÿ”‘Answer:

One possible solution is as follows:

randomIndex : [Nat] -> '{Abort, Random} Nat randomIndex list = do n = List.size list if n === 0 then abort else base.abilities.Random.natIn 0 n toOptional! '(lib.base.abilities.Random.lcg 7 (randomIndex [1, 2, 3]))
โงจ

toOptional!is a function which expects a delayed computation which performs theAbortability. The entire expression will need a'symbol in front of the call tolib.base.abilities.Random.lcg