From bb62981e45d7a072d54a3e6323aa605962dbee4f Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Mon, 6 Jun 2022 23:53:51 -0700 Subject: [PATCH 01/10] adds ability-handler docs and first concept exercise --- concepts/ability-handlers/.meta/config.json | 7 ++ concepts/ability-handlers/about.md | 56 ++++++++++++ concepts/ability-handlers/introduction.md | 14 +++ concepts/ability-handlers/links.json | 10 +++ config.json | 20 ++++- exercises/concept/counter/.docs/hints.md | 20 +++++ .../concept/counter/.docs/instructions.md | 85 +++++++++++++++++++ .../concept/counter/.docs/introduction.md | 56 ++++++++++++ exercises/concept/counter/.meta/config.json | 10 +++ exercises/concept/counter/.meta/design.md | 15 ++++ .../counter/.meta/examples/counter.example.u | 70 +++++++++++++++ .../concept/counter/.meta/testAnnotation.json | 22 +++++ exercises/concept/counter/.meta/testLoader.md | 8 ++ exercises/concept/counter/src/counter.u | 62 ++++++++++++++ exercises/concept/counter/test/counter.test.u | 22 +++++ 15 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 concepts/ability-handlers/.meta/config.json create mode 100644 concepts/ability-handlers/about.md create mode 100644 concepts/ability-handlers/introduction.md create mode 100644 concepts/ability-handlers/links.json create mode 100644 exercises/concept/counter/.docs/hints.md create mode 100644 exercises/concept/counter/.docs/instructions.md create mode 100644 exercises/concept/counter/.docs/introduction.md create mode 100644 exercises/concept/counter/.meta/config.json create mode 100644 exercises/concept/counter/.meta/design.md create mode 100644 exercises/concept/counter/.meta/examples/counter.example.u create mode 100644 exercises/concept/counter/.meta/testAnnotation.json create mode 100644 exercises/concept/counter/.meta/testLoader.md create mode 100644 exercises/concept/counter/src/counter.u create mode 100644 exercises/concept/counter/test/counter.test.u diff --git a/concepts/ability-handlers/.meta/config.json b/concepts/ability-handlers/.meta/config.json new file mode 100644 index 0000000..3809d5b --- /dev/null +++ b/concepts/ability-handlers/.meta/config.json @@ -0,0 +1,7 @@ +{ + "blurb": "You can write your own ability handlers in Unison, providing different behavior to a given effect", + "authors": [ + "rlmark" + ], + "contributors": [] +} diff --git a/concepts/ability-handlers/about.md b/concepts/ability-handlers/about.md new file mode 100644 index 0000000..2fff3a3 --- /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, `KeyValue.get` for example, 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 requested 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 + +Here's a handler that allows 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..4a0dd5b --- /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, `KeyValue.get` for example, 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 requested 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 8765d4d..cedc7e6 100644 --- a/config.json +++ b/config.json @@ -25,7 +25,17 @@ "average_run_time": 2.9 }, "exercises": { - "concept": [], + "concept": [ + { + "slug": "counter", + "name": "Counter", + "uuid": "5cb7e7e2-e5f1-418c-bd42-d56ead658e16", + "practices": [], + "concepts": ["ability-handlers"], + "prerequisites": [], + "difficulty": 2 + } + ], "practice": [ { "slug": "acronym", @@ -117,7 +127,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/counter/.docs/hints.md b/exercises/concept/counter/.docs/hints.md new file mode 100644 index 0000000..2d04122 --- /dev/null +++ b/exercises/concept/counter/.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/counter/.docs/instructions.md b/exercises/concept/counter/.docs/instructions.md new file mode 100644 index 0000000..efa5478 --- /dev/null +++ b/exercises/concept/counter/.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/counter/.docs/introduction.md b/exercises/concept/counter/.docs/introduction.md new file mode 100644 index 0000000..e2ecbfa --- /dev/null +++ b/exercises/concept/counter/.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, `KeyValue.get` for example, 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/counter/.meta/config.json b/exercises/concept/counter/.meta/config.json new file mode 100644 index 0000000..f4b38b4 --- /dev/null +++ b/exercises/concept/counter/.meta/config.json @@ -0,0 +1,10 @@ +{ + "blurb": "Get started writing your own ability handlers by writing a counter", + "icon": "palindrome-products", + "authors": ["rlmark", "pchuisano"], + "files": { + "solution": ["src/counter.u"], + "test": ["test/counter.test.u"], + "exemplar": [".meta/examples/counter.example.u"] + } +} diff --git a/exercises/concept/counter/.meta/design.md b/exercises/concept/counter/.meta/design.md new file mode 100644 index 0000000..11deb78 --- /dev/null +++ b/exercises/concept/counter/.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/counter/.meta/examples/counter.example.u b/exercises/concept/counter/.meta/examples/counter.example.u new file mode 100644 index 0000000..0d77d9d --- /dev/null +++ b/exercises/concept/counter/.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/counter/.meta/testAnnotation.json b/exercises/concept/counter/.meta/testAnnotation.json new file mode 100644 index 0000000..685ab0f --- /dev/null +++ b/exercises/concept/counter/.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/counter/.meta/testLoader.md b/exercises/concept/counter/.meta/testLoader.md new file mode 100644 index 0000000..41dec80 --- /dev/null +++ b/exercises/concept/counter/.meta/testLoader.md @@ -0,0 +1,8 @@ +# Testing transcript + +```ucm +.> load ./src/counter.u +.> add +.> load ./test/counter.test.u +.> add +``` diff --git a/exercises/concept/counter/src/counter.u b/exercises/concept/counter/src/counter.u new file mode 100644 index 0000000..c3d2a53 --- /dev/null +++ b/exercises/concept/counter/src/counter.u @@ -0,0 +1,62 @@ +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) + +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/counter/test/counter.test.u b/exercises/concept/counter/test/counter.test.u new file mode 100644 index 0000000..4799b8e --- /dev/null +++ b/exercises/concept/counter/test/counter.test.u @@ -0,0 +1,22 @@ +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 +] From 3bc7f241c5a0cd9092649b05dfb2af53f51d1b3f Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Mon, 6 Jun 2022 23:56:07 -0700 Subject: [PATCH 02/10] updates blurb wordage --- concepts/ability-handlers/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/ability-handlers/.meta/config.json b/concepts/ability-handlers/.meta/config.json index 3809d5b..097df72 100644 --- a/concepts/ability-handlers/.meta/config.json +++ b/concepts/ability-handlers/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "You can write your own ability handlers in Unison, providing different behavior to a given effect", + "blurb": "You can write your own ability handlers in Unison, providing behavior to a given computational effect", "authors": [ "rlmark" ], From b76a0e43c372ea079ba8217126b2ae48dcea9395 Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Mon, 6 Jun 2022 23:59:27 -0700 Subject: [PATCH 03/10] updating handler wording in intro and about --- concepts/ability-handlers/about.md | 2 +- concepts/ability-handlers/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/ability-handlers/about.md b/concepts/ability-handlers/about.md index 2fff3a3..ab4b9b9 100644 --- a/concepts/ability-handlers/about.md +++ b/concepts/ability-handlers/about.md @@ -1,6 +1,6 @@ # 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, `KeyValue.get` for example, 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 requested 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. +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. diff --git a/concepts/ability-handlers/introduction.md b/concepts/ability-handlers/introduction.md index 4a0dd5b..18e879d 100644 --- a/concepts/ability-handlers/introduction.md +++ b/concepts/ability-handlers/introduction.md @@ -1,6 +1,6 @@ # 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, `KeyValue.get` for example, 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 requested 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. +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. From 771fa8dade6ea1a7ca49bb961d2903cc3326ddce Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Tue, 7 Jun 2022 00:03:29 -0700 Subject: [PATCH 04/10] tweaks wording --- concepts/ability-handlers/about.md | 2 +- exercises/concept/counter/.docs/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/ability-handlers/about.md b/concepts/ability-handlers/about.md index ab4b9b9..9c5a99f 100644 --- a/concepts/ability-handlers/about.md +++ b/concepts/ability-handlers/about.md @@ -14,7 +14,7 @@ A handler for the the `KeyValue` ability above will need to say what should happ ## The parts of a handler -Here's a handler that allows interaction with a KeyValue store backed by an in memory `Map`: +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 diff --git a/exercises/concept/counter/.docs/introduction.md b/exercises/concept/counter/.docs/introduction.md index e2ecbfa..e69709d 100644 --- a/exercises/concept/counter/.docs/introduction.md +++ b/exercises/concept/counter/.docs/introduction.md @@ -1,6 +1,6 @@ # 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, `KeyValue.get` for example, 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. +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. From 598da68899ea01675d3696b18013f93987642cb9 Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Tue, 7 Jun 2022 00:07:22 -0700 Subject: [PATCH 05/10] removes icon key --- exercises/concept/counter/.meta/config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/exercises/concept/counter/.meta/config.json b/exercises/concept/counter/.meta/config.json index f4b38b4..51153c0 100644 --- a/exercises/concept/counter/.meta/config.json +++ b/exercises/concept/counter/.meta/config.json @@ -1,6 +1,5 @@ { "blurb": "Get started writing your own ability handlers by writing a counter", - "icon": "palindrome-products", "authors": ["rlmark", "pchuisano"], "files": { "solution": ["src/counter.u"], From 164d2761e3551117fe97461b64f3e4c18d5726b4 Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Tue, 7 Jun 2022 00:12:42 -0700 Subject: [PATCH 06/10] renaming concept exercise after story not concept --- config.json | 4 +- exercises/concept/counter/.docs/hints.md | 20 ----- .../concept/counter/.docs/instructions.md | 85 ------------------- .../concept/counter/.docs/introduction.md | 56 ------------ exercises/concept/counter/.meta/config.json | 9 -- exercises/concept/counter/.meta/design.md | 15 ---- .../counter/.meta/examples/counter.example.u | 70 --------------- .../concept/counter/.meta/testAnnotation.json | 22 ----- exercises/concept/counter/.meta/testLoader.md | 8 -- exercises/concept/counter/src/counter.u | 62 -------------- exercises/concept/counter/test/counter.test.u | 22 ----- 11 files changed, 2 insertions(+), 371 deletions(-) delete mode 100644 exercises/concept/counter/.docs/hints.md delete mode 100644 exercises/concept/counter/.docs/instructions.md delete mode 100644 exercises/concept/counter/.docs/introduction.md delete mode 100644 exercises/concept/counter/.meta/config.json delete mode 100644 exercises/concept/counter/.meta/design.md delete mode 100644 exercises/concept/counter/.meta/examples/counter.example.u delete mode 100644 exercises/concept/counter/.meta/testAnnotation.json delete mode 100644 exercises/concept/counter/.meta/testLoader.md delete mode 100644 exercises/concept/counter/src/counter.u delete mode 100644 exercises/concept/counter/test/counter.test.u diff --git a/config.json b/config.json index cedc7e6..fa7bb88 100644 --- a/config.json +++ b/config.json @@ -27,8 +27,8 @@ "exercises": { "concept": [ { - "slug": "counter", - "name": "Counter", + "slug": "usher", + "name": "Usher", "uuid": "5cb7e7e2-e5f1-418c-bd42-d56ead658e16", "practices": [], "concepts": ["ability-handlers"], diff --git a/exercises/concept/counter/.docs/hints.md b/exercises/concept/counter/.docs/hints.md deleted file mode 100644 index 2d04122..0000000 --- a/exercises/concept/counter/.docs/hints.md +++ /dev/null @@ -1,20 +0,0 @@ -# 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/counter/.docs/instructions.md b/exercises/concept/counter/.docs/instructions.md deleted file mode 100644 index efa5478..0000000 --- a/exercises/concept/counter/.docs/instructions.md +++ /dev/null @@ -1,85 +0,0 @@ -# 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/counter/.docs/introduction.md b/exercises/concept/counter/.docs/introduction.md deleted file mode 100644 index e69709d..0000000 --- a/exercises/concept/counter/.docs/introduction.md +++ /dev/null @@ -1,56 +0,0 @@ -# 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/counter/.meta/config.json b/exercises/concept/counter/.meta/config.json deleted file mode 100644 index 51153c0..0000000 --- a/exercises/concept/counter/.meta/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "blurb": "Get started writing your own ability handlers by writing a counter", - "authors": ["rlmark", "pchuisano"], - "files": { - "solution": ["src/counter.u"], - "test": ["test/counter.test.u"], - "exemplar": [".meta/examples/counter.example.u"] - } -} diff --git a/exercises/concept/counter/.meta/design.md b/exercises/concept/counter/.meta/design.md deleted file mode 100644 index 11deb78..0000000 --- a/exercises/concept/counter/.meta/design.md +++ /dev/null @@ -1,15 +0,0 @@ -# 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/counter/.meta/examples/counter.example.u b/exercises/concept/counter/.meta/examples/counter.example.u deleted file mode 100644 index 0d77d9d..0000000 --- a/exercises/concept/counter/.meta/examples/counter.example.u +++ /dev/null @@ -1,70 +0,0 @@ -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/counter/.meta/testAnnotation.json b/exercises/concept/counter/.meta/testAnnotation.json deleted file mode 100644 index 685ab0f..0000000 --- a/exercises/concept/counter/.meta/testAnnotation.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "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/counter/.meta/testLoader.md b/exercises/concept/counter/.meta/testLoader.md deleted file mode 100644 index 41dec80..0000000 --- a/exercises/concept/counter/.meta/testLoader.md +++ /dev/null @@ -1,8 +0,0 @@ -# Testing transcript - -```ucm -.> load ./src/counter.u -.> add -.> load ./test/counter.test.u -.> add -``` diff --git a/exercises/concept/counter/src/counter.u b/exercises/concept/counter/src/counter.u deleted file mode 100644 index c3d2a53..0000000 --- a/exercises/concept/counter/src/counter.u +++ /dev/null @@ -1,62 +0,0 @@ -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) - -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/counter/test/counter.test.u b/exercises/concept/counter/test/counter.test.u deleted file mode 100644 index 4799b8e..0000000 --- a/exercises/concept/counter/test/counter.test.u +++ /dev/null @@ -1,22 +0,0 @@ -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 -] From 405c6c6d41e7da60ed530948aca928625aefa1c1 Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Tue, 7 Jun 2022 00:16:59 -0700 Subject: [PATCH 07/10] picking icon override --- exercises/concept/usher/.docs/hints.md | 20 +++++ exercises/concept/usher/.docs/instructions.md | 85 +++++++++++++++++++ exercises/concept/usher/.docs/introduction.md | 56 ++++++++++++ exercises/concept/usher/.meta/config.json | 10 +++ exercises/concept/usher/.meta/design.md | 15 ++++ .../usher/.meta/examples/counter.example.u | 70 +++++++++++++++ .../concept/usher/.meta/testAnnotation.json | 22 +++++ exercises/concept/usher/.meta/testLoader.md | 8 ++ exercises/concept/usher/src/counter.u | 62 ++++++++++++++ exercises/concept/usher/test/counter.test.u | 22 +++++ 10 files changed, 370 insertions(+) create mode 100644 exercises/concept/usher/.docs/hints.md create mode 100644 exercises/concept/usher/.docs/instructions.md create mode 100644 exercises/concept/usher/.docs/introduction.md create mode 100644 exercises/concept/usher/.meta/config.json create mode 100644 exercises/concept/usher/.meta/design.md create mode 100644 exercises/concept/usher/.meta/examples/counter.example.u create mode 100644 exercises/concept/usher/.meta/testAnnotation.json create mode 100644 exercises/concept/usher/.meta/testLoader.md create mode 100644 exercises/concept/usher/src/counter.u create mode 100644 exercises/concept/usher/test/counter.test.u 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..78ef741 --- /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": ["src/counter.u"], + "test": ["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..41dec80 --- /dev/null +++ b/exercises/concept/usher/.meta/testLoader.md @@ -0,0 +1,8 @@ +# Testing transcript + +```ucm +.> load ./src/counter.u +.> add +.> load ./test/counter.test.u +.> add +``` diff --git a/exercises/concept/usher/src/counter.u b/exercises/concept/usher/src/counter.u new file mode 100644 index 0000000..c3d2a53 --- /dev/null +++ b/exercises/concept/usher/src/counter.u @@ -0,0 +1,62 @@ +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) + +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/test/counter.test.u b/exercises/concept/usher/test/counter.test.u new file mode 100644 index 0000000..4799b8e --- /dev/null +++ b/exercises/concept/usher/test/counter.test.u @@ -0,0 +1,22 @@ +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 +] From c739d6ebdaa0aae2d76ed10e5fa5eee2fbc5dbb0 Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Wed, 6 Jul 2022 11:52:24 -0700 Subject: [PATCH 08/10] updates structure --- exercises/concept/usher/.meta/config.json | 4 ++-- exercises/concept/usher/{test => }/counter.test.u | 0 exercises/concept/usher/{src => }/counter.u | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename exercises/concept/usher/{test => }/counter.test.u (100%) rename exercises/concept/usher/{src => }/counter.u (100%) diff --git a/exercises/concept/usher/.meta/config.json b/exercises/concept/usher/.meta/config.json index 78ef741..ec5fefa 100644 --- a/exercises/concept/usher/.meta/config.json +++ b/exercises/concept/usher/.meta/config.json @@ -3,8 +3,8 @@ "authors": ["rlmark", "pchuisano"], "icon": "movie-goer", "files": { - "solution": ["src/counter.u"], - "test": ["test/counter.test.u"], + "solution": ["counter.u"], + "test": ["counter.test.u"], "exemplar": [".meta/examples/counter.example.u"] } } diff --git a/exercises/concept/usher/test/counter.test.u b/exercises/concept/usher/counter.test.u similarity index 100% rename from exercises/concept/usher/test/counter.test.u rename to exercises/concept/usher/counter.test.u diff --git a/exercises/concept/usher/src/counter.u b/exercises/concept/usher/counter.u similarity index 100% rename from exercises/concept/usher/src/counter.u rename to exercises/concept/usher/counter.u From a125ba083fc49052ac87a07d8d26b975af88d03d Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Wed, 6 Jul 2022 11:54:51 -0700 Subject: [PATCH 09/10] fixes testLoader --- exercises/concept/usher/.meta/testLoader.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/usher/.meta/testLoader.md b/exercises/concept/usher/.meta/testLoader.md index 41dec80..5ad55c9 100644 --- a/exercises/concept/usher/.meta/testLoader.md +++ b/exercises/concept/usher/.meta/testLoader.md @@ -1,8 +1,8 @@ # Testing transcript ```ucm -.> load ./src/counter.u +.> load ./counter.u .> add -.> load ./test/counter.test.u +.> load ./counter.test.u .> add ``` From 9fbc99f95b46ab7ce68122accc016b302fab1b82 Mon Sep 17 00:00:00 2001 From: Rebecca Mark Date: Thu, 4 Aug 2022 09:43:51 -0700 Subject: [PATCH 10/10] moves counter examples to test file --- exercises/concept/usher/counter.test.u | 32 ++++++++++++++++++++++++++ exercises/concept/usher/counter.u | 30 ------------------------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/exercises/concept/usher/counter.test.u b/exercises/concept/usher/counter.test.u index 4799b8e..dec2a14 100644 --- a/exercises/concept/usher/counter.test.u +++ b/exercises/concept/usher/counter.test.u @@ -1,3 +1,35 @@ +-- 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]) diff --git a/exercises/concept/usher/counter.u b/exercises/concept/usher/counter.u index c3d2a53..049e316 100644 --- a/exercises/concept/usher/counter.u +++ b/exercises/concept/usher/counter.u @@ -30,33 +30,3 @@ usher party maxSeating = cases 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