diff --git a/concepts/ability-handlers/.meta/config.json b/concepts/ability-handlers/.meta/config.json new file mode 100644 index 0000000..097df72 --- /dev/null +++ b/concepts/ability-handlers/.meta/config.json @@ -0,0 +1,7 @@ +{ + "blurb": "You can write your own ability handlers in Unison, providing behavior to a given computational effect", + "authors": [ + "rlmark" + ], + "contributors": [] +} diff --git a/concepts/ability-handlers/about.md b/concepts/ability-handlers/about.md new file mode 100644 index 0000000..9c5a99f --- /dev/null +++ b/concepts/ability-handlers/about.md @@ -0,0 +1,56 @@ +# About + +In Unison, you can write your own abilities and handlers to supply different behaviors to your program. When a function calls one of the operations of an ability, Unison looks to the nearest enclosing handler to determine the next action to take. Handlers are special Unison functions where you can do things like pause and resume a computation, store state between calls to the ability, supply effectfully computed values to the rest of the program, or translate the ability into other Unison values. In short, they provide the implementation details for the ability. + +Handlers follow some specific syntax conventions, but are conceptually a pattern match function. When writing a handler we are pattern matching on the request operations of the ability and dictating what should happen in each case when that operation is called. + +``` +structural ability KeyValue k v where + get : k -> Optional v + put : k -> v -> () +``` + +A handler for the the `KeyValue` ability above will need to say what should happen when `KeyValue.put` and `KeyValue.get` are called in addition to showing what should occur when the a function is done calling the ability's operations, or never calls the operations in the first place. + +## The parts of a handler + +Let's look at a handler that enables interaction with a KeyValue store backed by an in-memory `Map`: + +``` +KeyValue.run : '{KeyValue k v} r -> r +KeyValue.run keyValueFunction = + impl : Map k v -> Request (KeyValue k v) r -> r + impl map = cases + {KeyValue.get k -> resume} -> handle resume (Map.get k map) with impl map + {KeyValue.put k v -> resume} -> handle resume () with impl (Map.put k v map) + {pure} -> pure + handle !keyValueFunction with impl Map.empty +``` + +Here's an overview of what this handler is doing: + +1. This handler starts the `KeyValue` with an initial empty map value as the storage backing. This `Map` will be updated in subsequent calls to the request constructors of the ability. +2. The handler `get`'s values from the map when a program calls the `get` function and returns the expected value to the rest of the program +3. The handler `put`'s values into the map when a program calls the `put` function and updates the internal state of the handler by calling `impl` with an updated map +3. The handler passes through a `pure` value after all the interactions with the ability operations are done, or if the program doesn't end up using the ability at all. + +### The type signatures of handlers + +The type signature `KeyValue.run : '{KeyValue k v} r -> r` is read "KeyValue.run is a function which takes a computation that uses a `KeyValue` store in the process of returning some value, `r`, and eliminates the ability, allowing us to return that `r` value." + +The single quote represents a thunk, or [delayed computation][delayed-computations]. + +Inside the `KeyValue.run` handler is a helper function with the signature `impl : Map k v -> Request (KeyValue k v) r -> r`. This is a common pattern for writing ability handlers. The helper function's first argument is the `Map` that we'll be updating to contain state internal to the handler. The second argument starting with `Request (KeyValue k v) r` is Unison's type which represents requests to perform the ability's operations (here those operations are `get` and `put`). + +### Resuming computations + +If you look at the cases in our pattern match, `{KeyValue.get k -> resume} -> ...`, you'll notice that we're doing more than just pattern matching on the operations of the ability like `get` or `put`, there's also a variable called `resume`; that's because a handler encapsulates a snapshot of the program state _as it is running._ Elsewhere in computer science literature the idea of "resuming computations" is called a [continuation][continuation-reference]. `resume` is a function whose argument is always the return type of the request operation in question, for example, `get : k -> Optional v` returns an `Optional v`, so that's the value provided to `resume` after looking up the key in the `Map`. + +The fact that the continuation is reflected as a variable in the handler opens up possibilities for, say, rerunning the continuation, or even storing it! + +### The handle ... with keywords + +Many of the values in our handler have variable names that are up to us! For example, there's nothing magical about the word `pure` in the pattern match. You could call it `done` or `r` or `pineapple`. Likewise `resume` in the pattern match is just a convention. You could name it `theRestOfMyProgram` and call `handle theRestOfMyProgram` if you like. But there are two Unison specific keywords that have to be used in the handler: `handle ... with`. The `handle with` expression sandwiches two things: the first is the computation which performs the ability we're handling, and the second is the specific handler implementation which includes the `Request` type. Without it, there's nothing to communicate "hey we're operating on the request constructors of the ability" in the call to `!myKeyValueFunction` from the expression `handle !keyValueFunction with impl Map.empty`. + +[continuation-reference]: https://en.wikipedia.org/wiki/Continuation +[delayed-computations]: https://www.unison-lang.org/learn/fundamentals/values-and-functions/delayed-computations/ diff --git a/concepts/ability-handlers/introduction.md b/concepts/ability-handlers/introduction.md new file mode 100644 index 0000000..18e879d --- /dev/null +++ b/concepts/ability-handlers/introduction.md @@ -0,0 +1,14 @@ +# Introduction + +In Unison, you can write your own abilities and handlers to supply different behaviors to your program. When a function calls one of the operations of an ability, Unison looks to the nearest enclosing handler to determine the next action to take. Handlers are special Unison functions where you can do things like pause and resume a computation, store state between calls to the ability, supply effectfully computed values to the rest of the program, or translate the ability into other Unison values. In short, they provide the implementation details for the ability. + +Handlers follow some specific syntax conventions, but are conceptually a pattern match function. When writing a handler we are pattern matching on the request operations of the ability and dictating what should happen in each case when that operation is called. + +``` +structural ability KeyValue k v where + get : k -> Optional v + put : k -> v -> () +``` + +A handler for the the `KeyValue` ability above will need to say what should happen when `KeyValue.put` and `KeyValue.get` are called in addition to showing what should occur when the a function is done calling the ability's operations, or never calls the operations in the first place. + diff --git a/concepts/ability-handlers/links.json b/concepts/ability-handlers/links.json new file mode 100644 index 0000000..638c060 --- /dev/null +++ b/concepts/ability-handlers/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://www.unisonweb.org/docs/abilities", + "description": "Language guide for ability handlers" + }, + { + "url": "https://www.unison-lang.org/learn/fundamentals/abilities/", + "description": "Mental model for abilities" + } +] diff --git a/config.json b/config.json index d4477b4..7c8c352 100644 --- a/config.json +++ b/config.json @@ -30,7 +30,17 @@ "average_run_time": 2.9 }, "exercises": { - "concept": [], + "concept": [ + { + "slug": "usher", + "name": "Usher", + "uuid": "5cb7e7e2-e5f1-418c-bd42-d56ead658e16", + "practices": [], + "concepts": ["ability-handlers"], + "prerequisites": [], + "difficulty": 2 + } + ], "practice": [ { "slug": "acronym", @@ -242,7 +252,13 @@ } ] }, - "concepts": [], + "concepts": [ + { + "uuid": "7791a83f-d824-49fb-9aad-38dd25f7144b", + "slug": "ability-handlers", + "name": "Ability Handlers" + } + ], "key_features": [ { "title": "Content addressed", diff --git a/exercises/concept/usher/.docs/hints.md b/exercises/concept/usher/.docs/hints.md new file mode 100644 index 0000000..2d04122 --- /dev/null +++ b/exercises/concept/usher/.docs/hints.md @@ -0,0 +1,20 @@ +# Hints + +## General + +- Read about the [mental model for abilities][mental-model] in the intro to abilities documentation. It breaks down what your "internal computer" should be doing when executing code which uses abilities +- Take a look at [an example handler for the `Ask` ability][ask-handler]. It looks like it uses a helper function [Ask.provide.handler][ask-handler-implementation] that pattern matches on Ask's request constructor and a pure or pass-through case. + +## 1. Implement a simple counter handler + +- `Counter.run` is a handler which should contain some internal state: the current total count. +- Its customary for handlers to contain state as arguments to functions in the handler. Think about "updating the state" of the handler as "calling the handler again with an updated value" + +## 2. Return the total count alongside the value produced by the function + +- Experiment with the "pure" or "pass-through" case of the ability handler. What happens if you call "bug" instead of returning a value in the handler? Could you return _two_ instances of the value `{pure} -> (pure,pure)`, changing the type signature of the handlers accordingly? + + +[mental-model]: https://www.unison-lang.org/learn/fundamentals/abilities/ +[ask-handler]: https://share.unison-lang.org/latest/namespaces/unison/base/;/terms/Ask/provide +[ask-handler-implementation]: https://share.unison-lang.org/latest/namespaces/unison/base/;/terms/Ask/provide/handler diff --git a/exercises/concept/usher/.docs/instructions.md b/exercises/concept/usher/.docs/instructions.md new file mode 100644 index 0000000..efa5478 --- /dev/null +++ b/exercises/concept/usher/.docs/instructions.md @@ -0,0 +1,85 @@ +# Instructions + +The following function makes use of Unison abilities to keep track of a running count of parties entering a theatre. 🎭 + +The `usher` function itself is fairly simple, it takes in a list of tickets representing a party and lets parties in the door until the theater is full. If a number of tickets in in a party is greater than the remaining seats, a separate overflow list is started, otherwise the total count is incremented and the party is added to the theater. + +However, the original author has left the ability handler writing up to you! Without a handler providing the concrete behavior of the required `Counter` ability, the function cannot actually be run or tested. + +``` +usher : [Ticket] -> Nat -> Theater -> {Counter} Theater +usher party maxSeating = cases + Main room -> + currentTotal = !getCount + nextPartySize = List.size party + if currentTotal + nextPartySize > maxSeating then + Overflow party + else + incrementBy nextPartySize + Main (room ++ party) + Overflow room -> + Overflow (room ++ party) +``` + +The `Counter` ability is given to you as: + +``` +structural ability Counter where + getCount : () -> Nat + incrementBy : Nat -> () +``` + +`getCount` is a "thunk" or ["delayed computation"][delayed-computations] that returns the current count when called. + +`incrementBy` takes a number to increment the count by and returns `Unit`. + +## 1. Implement a simple counter handler + +You'll need to implement an ability handler called `Counter.run` that takes in the value to start the count from and determines what should be done when the request operations, `getCount` and `incrementBy`, are called. + +``` +Counter.run : Nat -> '{Counter} a -> a +Counter.run initialValue functionUsingCounter = todo "implement run" +``` + +Once this handler is implemented, we can use it in simple programs like: + +``` +program : Theater +program = + runUsher = 'let usher [Ticket, Ticket] 10 (Main []) + Counter.run 0 runUsher +``` + +Or even use our handler as a result of processing a list of attendees: + +``` +programWithList : Theater +programWithList = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.run 0 runUsher +``` + +A number of other examples of calling the `Counter.run` handler are included for testing! + +## 2. Return the total count alongside the value produced by the function + +Without changing the implementation of the `usher` function, we'd like to be able to get the total count as well as the result of the function using the `Counter` ability. + +``` +Counter.runWithTotal : Nat -> '{Counter} a -> (Nat, a) +Counter.runWithTotal initialValue functionUsingCounter = todo "implement runWithTotal" +``` + +Applying this to our particular case, this handler should return a tuple of the running total with the ending state of the `Theater` + +``` +programWithTotal : (Nat, Theater) +programWithTotal = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.runWithTotal 0 runUsher +``` + +[delayed-computations]: https://www.unison-lang.org/learn/fundamentals/values-and-functions/delayed-computations/ diff --git a/exercises/concept/usher/.docs/introduction.md b/exercises/concept/usher/.docs/introduction.md new file mode 100644 index 0000000..e69709d --- /dev/null +++ b/exercises/concept/usher/.docs/introduction.md @@ -0,0 +1,56 @@ +# About + +In Unison, you can write your own abilities and handlers to supply different behaviors to your program. When a function calls one of the operations of an ability, Unison looks to the nearest enclosing handler to determine the next action to take. Handlers are special Unison functions where you can do things like pause and resume a computation, store state between calls to the ability, supply effectfully obtained values to the rest of the program, or translate the ability into other Unison values. In short, they provide the implementation details for the ability. + +Handlers follow some specific syntax conventions, but are conceptually a pattern match function. When writing a handler we are pattern matching on the request operations of the ability and dictating what should happen in each case when that operation is called. + +``` +structural ability KeyValue k v where + get : k -> Optional v + put : k -> v -> () +``` + +A handler for the the `KeyValue` ability above will need to say what should happen when `KeyValue.put` and `KeyValue.get` are called in addition to showing what should occur when the a function is done calling the ability's operations, or never calls the operations in the first place. + +## The parts of a handler + +Let's inspect a handler that allows interactions with a KeyValue store ability backed by an in-memory `Map`: + +``` +KeyValue.run : '{KeyValue k v} r -> r +KeyValue.run keyValueFunction = + impl : Map k v -> Request (KeyValue k v) r -> r + impl map = cases + {KeyValue.get k -> resume} -> handle resume (Map.get k map) with impl map + {KeyValue.put k v -> resume} -> handle resume () with impl (Map.put k v map) + {pure} -> pure + handle !keyValueFunction with impl Map.empty +``` + +Here's an overview of what this handler is doing: + +1. This handler starts the `KeyValue` with an initial empty map value as the storage backing. This `Map` will be updated in subsequent calls to the request constructors of the ability. +2. The handler `get`'s values from the map when a program calls the `get` function and returns the expected value to the rest of the program +3. The handler `put`'s values into the map when a program calls the `put` function and updates the internal state of the handler by calling `impl` with an updated map +3. The handler passes through a `pure` value after all the interactions with the ability operations are done, or if the program doesn't end up using the ability at all. + +### The type signatures of handlers + +The type signature `KeyValue.run : '{KeyValue k v} r -> r` is read "KeyValue.run is a function which takes a computation that uses a `KeyValue` store in the process of returning some value, `r`, and eliminates the ability, allowing us to return that `r` value." + +The single quote represents a thunk, or [delayed computation][delayed-computations]. + +Inside the `KeyValue.run` handler is a helper function with the signature `impl : Map k v -> Request (KeyValue k v) r -> r`. This is a common pattern for writing ability handlers. The helper function's first argument is the `Map` that we'll be updating to contain state internal to the handler. The second argument starting with `Request (KeyValue k v) r` is Unison's type which represents requests to perform the ability's operations (here those operations are `get` and `put`). + +### Resuming computations + +If you look at the cases in our pattern match, `{KeyValue.get k -> resume} -> ...`, you'll notice that we're doing more than just pattern matching on the operations of the ability like `get` or `put`, there's also a variable called `resume`; that's because a handler encapsulates a snapshot of the program state _as it is running._ Elsewhere in computer science literature the idea of "resuming computations" is called a [continuation][continuation-reference]. `resume` is a function whose argument is always the return type of the request operation in question, for example, `get : k -> Optional v` returns an `Optional v`, so that's the value provided to `resume` after looking up the key in the `Map`. + +The fact that the continuation is reflected as a variable in the handler opens up possibilities for, say, rerunning the continuation, or even storing it! + +### The handle ... with keywords + +Many of the values in our handler have variable names that are up to us! For example, there's nothing magical about the word `pure` in the pattern match. You could call it `done` or `r` or `pineapple`. Likewise `resume` in the pattern match is just a convention. You could name it `theRestOfMyProgram` and call `handle theRestOfMyProgram` if you like. But there are two Unison specific keywords that have to be used in the handler: `handle ... with`. The `handle with` expression sandwiches two things: the first is the computation which performs the ability we're handling, and the second is the specific handler implementation which includes the `Request` type. Without it, there's nothing to communicate "hey we're operating on the request constructors of the ability" in the call to `!myKeyValueFunction` from the expression `handle !keyValueFunction with impl Map.empty`. + +[continuation-reference]: https://en.wikipedia.org/wiki/Continuation +[delayed-computations]: https://www.unison-lang.org/learn/fundamentals/values-and-functions/delayed-computations/ diff --git a/exercises/concept/usher/.meta/config.json b/exercises/concept/usher/.meta/config.json new file mode 100644 index 0000000..ec5fefa --- /dev/null +++ b/exercises/concept/usher/.meta/config.json @@ -0,0 +1,10 @@ +{ + "blurb": "Get started writing your own ability handlers by writing a counter", + "authors": ["rlmark", "pchuisano"], + "icon": "movie-goer", + "files": { + "solution": ["counter.u"], + "test": ["counter.test.u"], + "exemplar": [".meta/examples/counter.example.u"] + } +} diff --git a/exercises/concept/usher/.meta/design.md b/exercises/concept/usher/.meta/design.md new file mode 100644 index 0000000..11deb78 --- /dev/null +++ b/exercises/concept/usher/.meta/design.md @@ -0,0 +1,15 @@ +# Design + +## Learning objectives + +- Learn how to write custom ability handlers in Unison + - Familiarize ability handler syntax + - Shows how handler can contain program state + +## Out of scope + +## Prerequisites + +## Concepts + +- `ability-handlers` diff --git a/exercises/concept/usher/.meta/examples/counter.example.u b/exercises/concept/usher/.meta/examples/counter.example.u new file mode 100644 index 0000000..0d77d9d --- /dev/null +++ b/exercises/concept/usher/.meta/examples/counter.example.u @@ -0,0 +1,70 @@ +structural ability Counter where + getCount : () -> Nat + incrementBy : Nat -> () + +Counter.run : Nat -> '{Counter} a -> a +Counter.run initialValue functionWhichUsesCounter = + impl : Nat -> Request (Counter) a -> a + impl n = cases + {Counter.getCount _ -> resume} -> handle resume n with impl n + {Counter.incrementBy incr -> resume } -> handle resume () with impl (n + incr) + {pure} -> pure + handle !functionWhichUsesCounter with impl initialValue + +Counter.runWithTotal : Nat -> '{Counter} a -> (Nat, a) +Counter.runWithTotal initialValue functionWhichUsesCounter = + impl : Nat -> Request (Counter) a -> (Nat, a) + impl n = cases + {Counter.getCount _ -> resume} -> handle resume n with impl n + {Counter.incrementBy incr -> resume } -> handle resume () with impl (n + incr) + {pure} -> (n, pure) + handle !functionWhichUsesCounter with impl initialValue + +-- Scenario using the counter ability below + +unique type Ticket = Ticket + +unique type Theater = Overflow [Ticket] | Main [Ticket] + +usher : [Ticket] -> Nat -> Theater -> {Counter} Theater +usher party maxSeating = cases + Main room -> + currentTotal = !getCount + nextPartySize = List.size party + if currentTotal + nextPartySize > maxSeating then + Overflow party + else + incrementBy nextPartySize + Main (room ++ party) + Overflow room -> + Overflow (room ++ party) + +program : Theater +program = + runUsher = 'let usher [Ticket, Ticket] 10 (Main []) + Counter.run 0 runUsher + +programWithList : Theater +programWithList = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket], [Ticket, Ticket, Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.run 0 runUsher + +programWithOverflow : Theater +programWithOverflow = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket], [Ticket, Ticket, Ticket, Ticket, Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.run 0 runUsher + +programWithTotal : (Nat, Theater) +programWithTotal = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.runWithTotal 0 runUsher + +noCounter : Text +noCounter = + counterNotCalled : '{Counter} Text + counterNotCalled = 'let + "counter ability is specified but not called" + Counter.run 0 counterNotCalled diff --git a/exercises/concept/usher/.meta/testAnnotation.json b/exercises/concept/usher/.meta/testAnnotation.json new file mode 100644 index 0000000..685ab0f --- /dev/null +++ b/exercises/concept/usher/.meta/testAnnotation.json @@ -0,0 +1,22 @@ +[ + { + "name": "counter.test.ex1" , + "test_code": "test.expect (program === Main [Ticket, Ticket])" + }, + { + "name": "counter.test.ex2" , + "test_code": "test.expect (programWithList === Main[Ticket, Ticket, Ticket, Ticket, Ticket, Ticket, Ticket, Ticket, Ticket])" + }, + { + "name": "counter.test.ex3" , + "test_code": "test.expect (programWithOverflow === Overflow[Ticket, Ticket, Ticket, Ticket, Ticket])" + }, + { + "name": "counter.test.ex4" , + "test_code": "test.expect (programWithTotal === (6, Main [Ticket, Ticket, Ticket, Ticket, Ticket, Ticket]))" + }, + { + "name": "coutner.test.ex5" , + "test_code": "test.expect (noCounter === \"counter ability is specified but not called\")" + } +] diff --git a/exercises/concept/usher/.meta/testLoader.md b/exercises/concept/usher/.meta/testLoader.md new file mode 100644 index 0000000..5ad55c9 --- /dev/null +++ b/exercises/concept/usher/.meta/testLoader.md @@ -0,0 +1,8 @@ +# Testing transcript + +```ucm +.> load ./counter.u +.> add +.> load ./counter.test.u +.> add +``` diff --git a/exercises/concept/usher/counter.test.u b/exercises/concept/usher/counter.test.u new file mode 100644 index 0000000..dec2a14 --- /dev/null +++ b/exercises/concept/usher/counter.test.u @@ -0,0 +1,54 @@ +-- Test programs + +program : Theater +program = + runUsher = 'let usher [Ticket, Ticket] 10 (Main []) + Counter.run 0 runUsher + +programWithList : Theater +programWithList = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket], [Ticket, Ticket, Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.run 0 runUsher + +programWithOverflow : Theater +programWithOverflow = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket], [Ticket, Ticket, Ticket, Ticket, Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.run 0 runUsher + +programWithTotal : (Nat, Theater) +programWithTotal = + attendees = [[Ticket, Ticket], [Ticket, Ticket, Ticket], [Ticket]] + runUsher = 'let List.foldLeft (theater -> party -> usher party 10 theater) (Main []) attendees + Counter.runWithTotal 0 runUsher + +noCounter : Text +noCounter = + counterNotCalled : '{Counter} Text + counterNotCalled = 'let + "counter ability is specified but not called" + Counter.run 0 counterNotCalled + +counter.test.ex1 = + test.expect (program === Main [Ticket, Ticket]) + +counter.test.ex2 = + test.expect (programWithList === Main[Ticket, Ticket, Ticket, Ticket, Ticket, Ticket, Ticket, Ticket, Ticket]) + +counter.test.ex3 = + test.expect (programWithOverflow === Overflow[Ticket, Ticket, Ticket, Ticket, Ticket]) + +counter.test.ex4 = + test.expect (programWithTotal === (6, Main [Ticket, Ticket, Ticket, Ticket, Ticket, Ticket])) + +counter.test.ex5 = + test.expect (noCounter === "counter ability is specified but not called") + +test> tests = runAll [ + counter.test.ex1, + counter.test.ex2, + counter.test.ex3, + counter.test.ex4, + counter.test.ex5 +] diff --git a/exercises/concept/usher/counter.u b/exercises/concept/usher/counter.u new file mode 100644 index 0000000..049e316 --- /dev/null +++ b/exercises/concept/usher/counter.u @@ -0,0 +1,32 @@ +structural ability Counter where + getCount : () -> Nat + incrementBy : Nat -> () + +Counter.run : Nat -> '{Counter} a -> a +Counter.run initialValue functionWhichUsesCounter = + todo "implement Counter.run handler" + +Counter.runWithTotal : Nat -> '{Counter} a -> (Nat, a) +Counter.runWithTotal initialValue functionWhichUsesCounter = + -- placeholder implementation for part 2 + a = run initialValue functionWhichUsesCounter + (0, a) + +-- Scenario using the counter ability below + +unique type Ticket = Ticket + +unique type Theater = Overflow [Ticket] | Main [Ticket] + +usher : [Ticket] -> Nat -> Theater -> {Counter} Theater +usher party maxSeating = cases + Main room -> + currentTotal = !getCount + nextPartySize = List.size party + if currentTotal + nextPartySize > maxSeating then + Overflow party + else + incrementBy nextPartySize + Main (room ++ party) + Overflow room -> + Overflow (room ++ party)