diff --git a/concepts/lists/.meta/config.json b/concepts/lists/.meta/config.json new file mode 100644 index 0000000..67ca30a --- /dev/null +++ b/concepts/lists/.meta/config.json @@ -0,0 +1,6 @@ +{ + "authors": [ + "rlmark" + ], + "blurb": "Using the List collection type in Unison" +} diff --git a/concepts/lists/about.md b/concepts/lists/about.md new file mode 100644 index 0000000..ecc9dc1 --- /dev/null +++ b/concepts/lists/about.md @@ -0,0 +1,61 @@ +# Unison Lists + +Lists are used in the Unison programming language to represent a finite, ordered collection of elements. They're written with comma-separated elements in square braces: + +``` +emptyList = [] + +listNats : [Nat] +listNats = [1,2,3,4] + +listText : [Text] +listText = ["hello", "world"] +``` + +Unison implements lists as a finger tree, allowing for fast access of the first and last element. You can read more about the underlying implementation in the [standard library List documentation][list-docs]. Appending single elements to either side of the list can be done with the `+:` and `:+` operators: + +``` +myList = [1,2,3,4] + +> 0 +: myList :+ 5 + ⧩ + [0, 1, 2, 3, 4, 5] +``` + +Concatenating lists is supported with the `++` operator: + +``` +> [1,2] ++ [3,4,5] + ⧩ + [1, 2, 3, 4, 5] +``` + +The standard library, `base` contains a variety of functions available for `List` transformations. Check them out by using the `find.all` command in the UCM or by exploring the `List` namespace in [Unison share][list-docs]. + +## List pattern matching + +It's common to pattern match on the head and tail elements of a list with the `+:` syntax: + +``` +match ["a", " b", "c"] with + head +: tail -> head + otherwise -> "otherwise" +``` + +But in Unison you can also pattern match on the last element of a list, just reverse the operator: + +``` +match ["a", " b", "c"] with + prefix :+ last -> last + otherwise -> "otherwise" +``` + +Multi list element matching is supported by surrounding the desired elements in square brackets, followed by the concatenation operator, `++`. This expression will match any list with a length of two or more elements: + +``` +match ["a", " b", "c"] with + [first, second] ++ remainder -> first + otherwise -> "otherwise" +``` + +[list-docs]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/data/List diff --git a/concepts/lists/introduction.md b/concepts/lists/introduction.md new file mode 100644 index 0000000..ecc9dc1 --- /dev/null +++ b/concepts/lists/introduction.md @@ -0,0 +1,61 @@ +# Unison Lists + +Lists are used in the Unison programming language to represent a finite, ordered collection of elements. They're written with comma-separated elements in square braces: + +``` +emptyList = [] + +listNats : [Nat] +listNats = [1,2,3,4] + +listText : [Text] +listText = ["hello", "world"] +``` + +Unison implements lists as a finger tree, allowing for fast access of the first and last element. You can read more about the underlying implementation in the [standard library List documentation][list-docs]. Appending single elements to either side of the list can be done with the `+:` and `:+` operators: + +``` +myList = [1,2,3,4] + +> 0 +: myList :+ 5 + ⧩ + [0, 1, 2, 3, 4, 5] +``` + +Concatenating lists is supported with the `++` operator: + +``` +> [1,2] ++ [3,4,5] + ⧩ + [1, 2, 3, 4, 5] +``` + +The standard library, `base` contains a variety of functions available for `List` transformations. Check them out by using the `find.all` command in the UCM or by exploring the `List` namespace in [Unison share][list-docs]. + +## List pattern matching + +It's common to pattern match on the head and tail elements of a list with the `+:` syntax: + +``` +match ["a", " b", "c"] with + head +: tail -> head + otherwise -> "otherwise" +``` + +But in Unison you can also pattern match on the last element of a list, just reverse the operator: + +``` +match ["a", " b", "c"] with + prefix :+ last -> last + otherwise -> "otherwise" +``` + +Multi list element matching is supported by surrounding the desired elements in square brackets, followed by the concatenation operator, `++`. This expression will match any list with a length of two or more elements: + +``` +match ["a", " b", "c"] with + [first, second] ++ remainder -> first + otherwise -> "otherwise" +``` + +[list-docs]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/data/List diff --git a/concepts/lists/links.json b/concepts/lists/links.json new file mode 100644 index 0000000..f85af3c --- /dev/null +++ b/concepts/lists/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://www.unison-lang.org/learn/fundamentals/values-and-functions/common-collection-types/#lists", + "description": "Lists and other common collection types in Unison" + }, + { + "url": "https://www.unison-lang.org/learn/fundamentals/control-flow/pattern-matching2/#pattern-matching-on", + "description": "List pattern matching" + }, + { + "url": "https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/data/List", + "description": "List documentation from the standard library" + }, + { + "url": "https://www.unison-lang.org/learn/fundamentals/control-flow/looping/", + "description": "Tips for using recursion using a List example" + } +] diff --git a/config.json b/config.json index e692a42..adef4ac 100644 --- a/config.json +++ b/config.json @@ -40,6 +40,16 @@ "concepts": ["basics"], "difficulty": 1, "status": "wip" + }, + { + "slug": "language-list", + "name": "Language List", + "uuid": "5ebdb25b-39a0-4cd8-9f73-04ec565ef15f", + "practices": [], + "prerequisites": [], + "concepts": ["lists"], + "difficulty": 2, + "status": "wip" } ], "practice": [ @@ -282,6 +292,11 @@ "uuid": "24332ec2-07b2-49e5-aa58-4829d31dc44a", "slug": "basics", "name": "Basics" + }, + { + "uuid": "44f2d0c1-73db-487a-a2d2-aea4fa7be73f", + "slug": "lists", + "name": "Lists" } ], "key_features": [ diff --git a/exercises/concept/language-list/.docs/hints.md b/exercises/concept/language-list/.docs/hints.md new file mode 100644 index 0000000..3cf4356 --- /dev/null +++ b/exercises/concept/language-list/.docs/hints.md @@ -0,0 +1,41 @@ +# Hints + +## General + +- Use the built-in [list type][list]. +- In functional programming it's common to iterate through, inspect, or manipulate lists by [recursively pattern matching][functional-looping] on their elements until reaching the end of the list. In fact, many of the common functions defined for the list data type are based on these principles. + +## 1. Define a function to return an empty language list + +- You need to define a [Unison function function][named-function] with 0 arguments. + +## 2. Define a function to add a language to the list + +- You need to define a function with 2 arguments. The first argument is a [text literal value][string]. The second argument is a [list][list] of language [text literals][string]. +- The list data type has a number of operators for manipulating lists, the "cons" (aka prepend) operator looks like `+:`. + +## 3. Define a function to remove the left-most language from the list + +- You need to define a function with 1 argument. The first argument is a [list][list] of language [text literals][string]. +- Check out Unison's syntax for [pattern matching on list elements][list-patterns]. Pattern matching allows you to decompose the list into its constituent parts. + +## 4. Define a function to return how many elements are in the list + +- You need to define a function which takes in a [list][list] of language [text literals][string] and returns the size as a `Nat`. +- It's common to keep a running count of the number of elements seen using [recursion][functional-looping]. + +## 5. Define a function to determine if the list includes a given language + +- You need to define a function with 2 arguments. The first is a [text][string] value and the second argument is a [list][list] of language [text literals][string]. + +## 6. Define a function to reverse a given list + +- You need to define a function with 1 argument: the `List` of `Text` values. +- What happens if you call `List.foldLeft` on your list, passing in the `+:` operator as the first argument? What happens if you call `List.foldRight` with the `+:` argument? + +[list]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/data/List +[string]: https://www.unison-lang.org/learn/language-reference/literals/ +[list-patterns]: https://www.unison-lang.org/learn/fundamentals/control-flow/pattern-matching2/#pattern-matching-on +[size]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/terms/data/List/size +[optional]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/Optional +[functional-looping]: https://www.unison-lang.org/learn/fundamentals/control-flow/looping/#functional-looping \ No newline at end of file diff --git a/exercises/concept/language-list/.docs/instructions.md b/exercises/concept/language-list/.docs/instructions.md new file mode 100644 index 0000000..809f5da --- /dev/null +++ b/exercises/concept/language-list/.docs/instructions.md @@ -0,0 +1,84 @@ +# Instructions + +In this exercise you need to implement some functions to manipulate a list of programming languages. Some of the functions you'll be asked to define may exist for `List`, which is good to know for future reference, but for this exercise try not to use them! + +## 1. Define a function to return an empty language list + +Define the `new` function that takes no arguments and returns an empty list. + +``` +languageList.new : [Text] +languageList.new = todo "return empty list" +``` + +## 2. Define a function to add a language to the list + +Define the `add` function that takes 2 arguments (a list of languages and a string literal of a language). It should return the resulting list with the new language added to the front of the given list. + +``` +> languageList.new + |> languageList.add "Clojure" + |> languageList.add "Unison" + ⧩ + ["Unison", "Clojure"] +``` + +## 3. Define a function to remove the left-most language from the list + +Define the `drop1` function that takes 1 argument (a list of languages). It should return the list without the first item. Calling `drop1` on an empty list should return an empty list. + +``` +> languageList.new + |> languageList.add "Clojure" + |> languageList.add "Unison" + |> languageList.drop1 + ⧩ + ["Clojure"] +``` + +## 4. Define a function to return how many elements are in the list + +Define the `size` function that takes 1 argument (a list of languages). It should return the number of languages in the list. + +``` +> languageList.new + |> languageList.add "Elm" + |> languageList.add "Prolog" + |> languageList.size + ⧩ + 2 +``` + +## 5. Define a function to determine if the list includes a given language + +Define the `contains` function which takes 1 argument (a list of languages). It should return a boolean value. It should return true if the desired language is one of the languages in the list. + +``` +> languageList.new + |> languageList.add "Unison" + |> languageList.add "Clojure" + |> languageList.contains "Unison" + ⧩ + true +``` + +## 6. Define a function to reverse a given list + +Define the `reverse` which takes in a list and returns the list with the elements in reverse order. + +``` +list1 = + languageList.new + |> languageList.add "Clojure" + |> languageList.add "Scala" + |> languageList.add "Unison" + |> languageList.add "Elm" + +> list1 + ⧩ + ["Elm", "Unison", "Scala", "Clojure"] + +> languageList.reverse list1 + ⧩ + ["Clojure", "Scala", "Unison", "Elm"] +``` diff --git a/exercises/concept/language-list/.docs/introduction.md b/exercises/concept/language-list/.docs/introduction.md new file mode 100644 index 0000000..ecc9dc1 --- /dev/null +++ b/exercises/concept/language-list/.docs/introduction.md @@ -0,0 +1,61 @@ +# Unison Lists + +Lists are used in the Unison programming language to represent a finite, ordered collection of elements. They're written with comma-separated elements in square braces: + +``` +emptyList = [] + +listNats : [Nat] +listNats = [1,2,3,4] + +listText : [Text] +listText = ["hello", "world"] +``` + +Unison implements lists as a finger tree, allowing for fast access of the first and last element. You can read more about the underlying implementation in the [standard library List documentation][list-docs]. Appending single elements to either side of the list can be done with the `+:` and `:+` operators: + +``` +myList = [1,2,3,4] + +> 0 +: myList :+ 5 + ⧩ + [0, 1, 2, 3, 4, 5] +``` + +Concatenating lists is supported with the `++` operator: + +``` +> [1,2] ++ [3,4,5] + ⧩ + [1, 2, 3, 4, 5] +``` + +The standard library, `base` contains a variety of functions available for `List` transformations. Check them out by using the `find.all` command in the UCM or by exploring the `List` namespace in [Unison share][list-docs]. + +## List pattern matching + +It's common to pattern match on the head and tail elements of a list with the `+:` syntax: + +``` +match ["a", " b", "c"] with + head +: tail -> head + otherwise -> "otherwise" +``` + +But in Unison you can also pattern match on the last element of a list, just reverse the operator: + +``` +match ["a", " b", "c"] with + prefix :+ last -> last + otherwise -> "otherwise" +``` + +Multi list element matching is supported by surrounding the desired elements in square brackets, followed by the concatenation operator, `++`. This expression will match any list with a length of two or more elements: + +``` +match ["a", " b", "c"] with + [first, second] ++ remainder -> first + otherwise -> "otherwise" +``` + +[list-docs]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/data/List diff --git a/exercises/concept/language-list/.meta/config.json b/exercises/concept/language-list/.meta/config.json new file mode 100644 index 0000000..006ac47 --- /dev/null +++ b/exercises/concept/language-list/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "rlmark" + ], + "files": { + "solution": [ + "languageList.u" + ], + "test": [ + "languageList.test.u" + ], + "exemplar": [ + ".meta/examples/languageList.example.u" + ] + }, + "forked_from": [ + "elixir/language-list" + ], + "blurb": "Learn about lists by keeping track of the programing languages you're currently learning on Exercism." +} diff --git a/exercises/concept/language-list/.meta/design.md b/exercises/concept/language-list/.meta/design.md new file mode 100644 index 0000000..59bb165 --- /dev/null +++ b/exercises/concept/language-list/.meta/design.md @@ -0,0 +1,32 @@ +# Design + +## Learning objectives + +- Know of the existence of the `List` type. +- Know how list can be constructed with `[]` and `head +: tail` syntax +- Use basic functions related to Lists. +- Parts 1,2,3 (empty, add, drop1) teach basic list primitives like, `[]`, `+:`, and `drop`, or associated pattern matching conventions. +- Part 4 (size) teaches folding over the list, or tracking state via recursion and pattern matching +- Part 5 (contains) teaches the contains function or folding over the list +- Part 6 (reverse) teaches foldRight or `+:` in the context of pattern matching + +## Out of scope + +- `Text` functions and string manipulation +- Memory and performance characteristics. +- Polymorphism + +## Concepts + +- `lists` + - know of the existence of the `List` type + - know of the idea of `List` design + - know some basic patterns / functions + +## Resources + +- Standard library List documentation: [Lists][base-list] +- List pattern matching: [Lists][pattern-matching-lists] + +[base-list]: https://share.unison-lang.org/@unison/code/latest/namespaces/public/base/latest/;/types/data/List +[pattern-matching-lists]: https://www.unison-lang.org/learn/fundamentals/control-flow/pattern-matching2/#pattern-matching-on diff --git a/exercises/concept/language-list/.meta/examples/languageList.example.u b/exercises/concept/language-list/.meta/examples/languageList.example.u new file mode 100644 index 0000000..98dc1d7 --- /dev/null +++ b/exercises/concept/language-list/.meta/examples/languageList.example.u @@ -0,0 +1,26 @@ +languageList.new : [Text] +languageList.new = [] + +languageList.add : Text -> [Text] -> [Text] +languageList.add input list = input +: list + +languageList.drop1 : [Text] -> [Text] +languageList.drop1 = cases + head +: tail -> tail + [] -> [] + +languageList.size : [Text] -> Nat +languageList.size list = + go acc = cases + [] -> acc + h :+ t -> go (Nat.increment acc) t + go 0 list + +languageList.contains : Text -> [Text] -> Boolean +languageList.contains input = cases + [] -> false + h +: t | h === input -> true + h +: t -> contains input t + +languageList.reverse : [Text] -> [Text] +languageList.reverse list = List.foldLeft (b a -> a List.+: b ) [] list \ No newline at end of file diff --git a/exercises/concept/language-list/.meta/testAnnotation.json b/exercises/concept/language-list/.meta/testAnnotation.json new file mode 100644 index 0000000..0d9d2fc --- /dev/null +++ b/exercises/concept/language-list/.meta/testAnnotation.json @@ -0,0 +1,50 @@ +[ + { + "name": "languageList.test.ex1", + "test_code": "languageList.test.ex1 =\n\tTest.label \"languageList.new should return empty list\" <| Test.expect (languageList.new === [])" + }, + { + "name": "languageList.test.ex2", + "test_code": "languageList.test.ex2 =\n\tTest.label \"languageList.add should prepend an element\" <| Test.expect (languageList.add [\"Elm\"] === [\"Unison\", \"Elm\"])" + }, + { + "name": "languageList.test.ex3", + "test_code": "languageList.test.ex3 =\n\tTest.label \"languageList.drop1 should drop first element\" <| Test.expect (languageList.drop1 [\"Elm\", \"Cobol\", \"Haskell\"] === [ \"Cobol\", \"Haskell\"])" + }, + { + "name": "languageList.test.ex4", + "test_code": "languageList.test.ex4 =\n\tTest.label \"languageList.drop1 should return empty list\" <| Test.expect (languageList.drop1 [] === [])" + }, + { + "name": "languageList.test.ex5", + "test_code": "languageList.test.ex5 =\n\tTest.label \"languageList.size should return size of list\" <| Test.expect (languageList.size [\"Elm\", \"Cobol\", \"Haskell\"] === 3)" + }, + { + "name": "languageList.test.ex6", + "test_code": "languageList.test.ex6 =\n\tTest.label \"languageList.size should handle empty list\" <| Test.expect (languageList.size [] === 0)" + }, + { + "name": "languageList.test.ex7", + "test_code": "languageList.test.ex7 =\n\tTest.label \"languageList.contains should return true if list contains element\" <| Test.expect (languageList.contains \"Unison\" [\"Scala\", \"Python\", \"Unison\"] === true)" + }, + { + "name": "languageList.test.ex8", + "test_code": "languageList.test.ex8 =\n\tTest.label \"languageList.contains should return false if list does not contain element\" <| Test.expect (languageList.contains \"Unison\" [\"Scala\", \"Python\", \"Ruby\"] === false)" + }, + { + "name": "languageList.test.ex9", + "test_code": "languageList.test.ex9 =\n\tTest.label \"languageList.contains should handle empty list\" <| Test.expect (languageList.contains \"Unison\" [] === false)" + }, + { + "name": "languageList.test.ex10", + "test_code": "languageList.test.ex10 =\n\tTest.label \"languageList.reverse should reverse list\" <| Test.expect (languageList.reverse [\"Elm\", \"Unison\", \"Scala\", \"Clojure\"] === [\"Clojure\", \"Scala\", \"Unison\", \"Elm\"])" + }, + { + "name": "languageList.test.ex11", + "test_code": "languageList.test.ex11 =\n\tTest.label \"languageList.reverse should handle list of one\" <| Test.expect (languageList.reverse [\"Unison\"] === [\"Unison\"])" + }, + { + "name": "languageList.test.ex12", + "test_code": "languageList.test.ex12 =\n\tTest.label \"languageList.reverse should handle list of one\" <| Test.expect (languageList.reverse [] === [])" + } +] \ No newline at end of file diff --git a/exercises/concept/language-list/.meta/testLoader.md b/exercises/concept/language-list/.meta/testLoader.md new file mode 100644 index 0000000..836e7fb --- /dev/null +++ b/exercises/concept/language-list/.meta/testLoader.md @@ -0,0 +1,9 @@ +# Testing transcript + +```ucm +.> load ./languageList.u +.> add +.> load ./languageList.test.u +.> add +.> move.term languageList.tests tests +``` diff --git a/exercises/concept/language-list/languageList.test.u b/exercises/concept/language-list/languageList.test.u new file mode 100644 index 0000000..1492c6d --- /dev/null +++ b/exercises/concept/language-list/languageList.test.u @@ -0,0 +1,50 @@ +languageList.test.ex1 = + Test.label "languageList.new should return empty list" <| Test.expect (languageList.new === []) + +languageList.test.ex2 = + Test.label "languageList.add should prepend an element" <| Test.expect (languageList.add "Unison" ["Elm"] === ["Unison", "Elm"]) + +languageList.test.ex3 = + Test.label "languageList.drop1 should drop first element" <| Test.expect (languageList.drop1 ["Elm", "Cobol", "Haskell"] === [ "Cobol", "Haskell"]) + +languageList.test.ex4 = + Test.label "languageList.drop1 should return empty list" <| Test.expect (languageList.drop1 [] === []) + +languageList.test.ex5 = + Test.label "languageList.size should return size of list" <| Test.expect (languageList.size ["Elm", "Cobol", "Haskell"] === 3) + +languageList.test.ex6 = + Test.label "languageList.size should handle empty list" <| Test.expect (languageList.size [] === 0) + +languageList.test.ex7 = + Test.label "languageList.contains should return true if list contains element" <| Test.expect (languageList.contains "Unison" ["Scala", "Python", "Unison"] === true) + +languageList.test.ex8 = + Test.label "languageList.contains should return false if list does not contain element" <| Test.expect (languageList.contains "Unison" ["Scala", "Python", "Ruby"] === false) + +languageList.test.ex9 = + Test.label "languageList.contains should handle empty list" <| Test.expect (languageList.contains "Unison" [] === false) + +languageList.test.ex10 = + Test.label "languageList.reverse should reverse list" <| Test.expect (languageList.reverse ["Elm", "Unison", "Scala", "Clojure"] === ["Clojure", "Scala", "Unison", "Elm"]) + +languageList.test.ex11 = + Test.label "languageList.reverse should handle list of one" <| Test.expect (languageList.reverse ["Unison"] === ["Unison"]) + +languageList.test.ex12 = + Test.label "languageList.reverse should handle list of one" <| Test.expect (languageList.reverse [] === []) + +test> languageList.tests = runAll [ + languageList.test.ex1, + languageList.test.ex2, + languageList.test.ex3, + languageList.test.ex4, + languageList.test.ex5, + languageList.test.ex6, + languageList.test.ex7, + languageList.test.ex8, + languageList.test.ex9, + languageList.test.ex10, + languageList.test.ex11, + languageList.test.ex12 +] \ No newline at end of file diff --git a/exercises/concept/language-list/languageList.u b/exercises/concept/language-list/languageList.u new file mode 100644 index 0000000..f614ac5 --- /dev/null +++ b/exercises/concept/language-list/languageList.u @@ -0,0 +1,17 @@ +languageList.new : [Text] +languageList.new = ["return empty list"] + +languageList.add : Text -> [Text] -> [Text] +languageList.add input list = ["return a list with the input preprended"] + +languageList.drop1 : [Text] -> [Text] +languageList.drop1 list = ["return a list with the first element dropped"] + +languageList.size : [Text] -> Nat +languageList.size list = 100000 + +languageList.contains : Text -> [Text] -> Boolean +languageList.contains input list = false + +languageList.reverse : [Text] -> [Text] +languageList.reverse list = ["return the reversed list"] \ No newline at end of file