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 theStore
ability. For additional practicewritinghandlers, we've given a handler stub calledrunWith
to implement.runWith
returns both the result of running the function which uses the Store ability and the final state of the Store.
This exercise is about implementing basic Stream ability operations. Writing your own handlers for common functional combinators likemap
,flatMap
andfilter
.
Ask
ability inStream.pipe
Ask
ability 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 typea
from whatever code handles the ability.
provide
is a handler for ask which provides a constant value.
A common usage ofAsk
is to avoid needing to pass around common configuration settings, justprovide myConfig 'myMain
to make myConfig available anywhere inmyMain
with a call toask
.
Computations that useAsk
can also be thought of as stream consumers. Try writing a functionpipe
,which can be used to statefully transform a stream:
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 writtenfundamentals.abilities.abilityExercises.Stream.pipe
,try writingStream.map
,Stream.filter
,andStream.take
usingfundamentals.abilities.abilityExercises.Stream.pipe
.
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 thecheckBalance
helper function given for this exercise makes use of an ability requirement,{Stack}
.The Stack ability is defined for you, but the handler,Stack.run
is 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.
๐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 theStore
ability for memoization when optimizing their solution.
๐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.
๐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 theEach
ability for non-determinism in thebase
library.
You might also want choose to use an ability for optimizing the search space when finding who owns the zebra. ๐ค
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!"
view Abort
to see the ability definition. The function signatures after thewhere
keyword are the request operations.Using theOptional.toAbort
function.
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 theStore
ability to keep track of aList
of numbers, write a function which takes in some numbern
ofNat
as an upper bound of a list from0
up to (but not including)n
and 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
One possible solution is as follows:
averageOfRange : Nat ->{Store [Nat]} Float
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 = 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 thebase
libraries for operating onStore
and 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 theFloat
value to check your function.
One possible solution is as follows:
handleAverageOfRange : Float
handleAverageOfRange = withInitialValue [] '(averageOfRange 5)
withInitialValue
handles a function that requires theStore
ability 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?
One possible solution is as follows:
toOptional!
is a function which expects a delayed computation which performs theAbort
ability. The entire expression will need a'
symbol in front of the call tolcg