From 8f8817fa79eedef25866bbda9adec7f194552323 Mon Sep 17 00:00:00 2001 From: meatball Date: Mon, 30 Sep 2024 20:04:14 +0200 Subject: [PATCH] Add list concept --- concepts/lists/.meta/config.json | 6 ++ concepts/lists/about.md | 87 +++++++++++++++++++ concepts/lists/introduction.md | 87 +++++++++++++++++++ concepts/lists/links.json | 1 + .../concept/language-list/.docs/hints.md | 8 ++ .../language-list/.docs/instructions.md | 66 ++++++++++++++ .../language-list/.docs/introduction.md | 87 +++++++++++++++++++ .../.meta/STUB-ALLOWED-TO-NOT-COMPILE | 3 + .../concept/language-list/.meta/config.json | 25 ++++++ .../concept/language-list/.meta/design.md | 36 ++++++++ .../language-list/.meta/exemplar/package.yaml | 18 ++++ .../.meta/exemplar/src/LanguageList.hs | 19 ++++ exercises/concept/language-list/package.yaml | 18 ++++ .../concept/language-list/src/LanguageList.hs | 25 ++++++ exercises/concept/language-list/stack.yaml | 1 + exercises/concept/language-list/test/Tests.hs | 63 ++++++++++++++ 16 files changed, 550 insertions(+) create mode 100644 concepts/lists/.meta/config.json create mode 100644 concepts/lists/about.md create mode 100644 concepts/lists/introduction.md create mode 100644 concepts/lists/links.json create mode 100644 exercises/concept/language-list/.docs/hints.md create mode 100644 exercises/concept/language-list/.docs/instructions.md create mode 100644 exercises/concept/language-list/.docs/introduction.md create mode 100644 exercises/concept/language-list/.meta/STUB-ALLOWED-TO-NOT-COMPILE create mode 100644 exercises/concept/language-list/.meta/config.json create mode 100644 exercises/concept/language-list/.meta/design.md create mode 100644 exercises/concept/language-list/.meta/exemplar/package.yaml create mode 100644 exercises/concept/language-list/.meta/exemplar/src/LanguageList.hs create mode 100644 exercises/concept/language-list/package.yaml create mode 100644 exercises/concept/language-list/src/LanguageList.hs create mode 100644 exercises/concept/language-list/stack.yaml create mode 100644 exercises/concept/language-list/test/Tests.hs diff --git a/concepts/lists/.meta/config.json b/concepts/lists/.meta/config.json new file mode 100644 index 000000000..e342fd8d7 --- /dev/null +++ b/concepts/lists/.meta/config.json @@ -0,0 +1,6 @@ +{ + "blurb": "Lists are a basic data type in Haskell for holding a collection of values. A list can hold values of different types. Lists are immutable, meaning they cannot be modified. Lists in Haskell are implemented as linked lists. Therefore, accessing an element in a list takes linear time depending on the length of the list.", + "authors": [ + "meatball133" + ] +} \ No newline at end of file diff --git a/concepts/lists/about.md b/concepts/lists/about.md new file mode 100644 index 000000000..bf76c86b8 --- /dev/null +++ b/concepts/lists/about.md @@ -0,0 +1,87 @@ +# About + +[Lists][list] are a basic data type in Haskell for holding a collection of values. +Lists are _immutable_, meaning they cannot be modified. +Any operation that changes a list returns a new list. +There are several methods in the prelude which allows you to work with Lists. + +Lists in Haskell are implemented as [linked lists][linked-list-wiki], and not as arrays of contiguous memory location. +Therefore, accessing an element in a list takes linear time depending on the length of the list. + +Lists can be written in literal form, head-tail notation, (which uses the `cons` operator `:`), or a combination of both: + +```haskell +-- Literal Form +[] +[1] +[1, 2, 3] + +-- Head-tail Notation +[] +-- same as [1] +1 : [] +-- same as [1, 2, 3] +1 : (2 : (3 : [])) + +-- Mixed +-- same as [1, 2, 3] +1 : [2, 3] +``` + +Head-tail notation can be used to append items to a list. + +```haskell +list = [2, 1] + +[3, 2, 1] == 3 : list +-- -> True +``` + +Appending elements to a list during iteration is considered an anti-pattern. +Appending an element requires walking through the entire list and adding the element at the end, therefore, appending a new element in each iteration would require walking through the entire list in each iteration. + +We can achieve the same result by prepending an element to the reversed list, and then reversing the result. Prepending is a fast operation and requires constant time. + +```haskell +-- Appending to the end of a list (potentially slow) +[1, 2, 3] ++ [4] ++ [5] ++ [6] + +-- Prepend to the start of a list (faster, due to the nature of linked lists) +6 : (5 : (4 : [3, 2, 1])) +-- then reverse! +``` + +There are several common Prelude functions for lists: + +- [`head`][head] returns the _head_ of a list -- the _first_ item in a list. +- [`tail`][tail] returns the _tail_ of the list -- the list _minus_ the _first_ item. +- [`length`][length] returns the number items in the list. +- [`elem`][in] returns a boolean value indicating whether the item is an element in the list. + +There is also the [`List` module][list]. + +Lists can only contain one data type. + +```haskell +list = [1, "string"] +-- Error: No instance for (Num String) arising from the literal ‘1’ +``` + +## Type annotation + +The type annotation of a list is `[a]` where a is the type which the lists holds, for example `String` or `Int`. + +``` haskell +a :: [Int] +a = [1, 2, 3] +``` + +[enum]: https://hexdocs.pm/elixir/Enum.html +[enum-protocol]: https://hexdocs.pm/elixir/Enumerable.html +[hd]: https://hexdocs.pm/elixir/Kernel.html#hd/1 +[in]: https://hexdocs.pm/elixir/Kernel.html#in/2 +[length]: https://hexdocs.pm/elixir/Kernel.html#length/1 +[list]: https://hexdocs.pm/elixir/List.html +[stream]: https://hexdocs.pm/elixir/Stream.html +[tl]: https://hexdocs.pm/elixir/Kernel.html#tl/1 +[linked-list-wiki]: https://en.wikipedia.org/wiki/Linked_list diff --git a/concepts/lists/introduction.md b/concepts/lists/introduction.md new file mode 100644 index 000000000..d5193935d --- /dev/null +++ b/concepts/lists/introduction.md @@ -0,0 +1,87 @@ +# About + +[Lists][list] are a basic data type in Haskell for holding a collection of values. +Lists are _immutable_, meaning they cannot be modified. +Any operation that changes a list returns a new list. +There are several methods in the prelude which allows you to work with Lists. + +Lists in Haskell are implemented as [linked lists][linked-list-wiki], and not as arrays of contiguous memory location. +Therefore, accessing an element in a list takes linear time depending on the length of the list. + +Lists can be written in literal form, head-tail notation, (which uses the `cons` operator `:`), or a combination of both: + +```haskell +-- Literal Form +[] +[1] +[1, 2, 3] + +-- Head-tail Notation +[] +-- same as [1] +1 : [] +-- same as [1, 2, 3] +1 : (2 : (3 : [])) + +-- Mixed +-- same as [1, 2, 3] +1 : [2, 3] +``` + +Head-tail notation can be used to append items to a list. + +```haskell +list = [2, 1] + +[3, 2, 1] == 3 : list +-- true +``` + +Appending elements to a list during iteration is considered an anti-pattern. +Appending an element requires walking through the entire list and adding the element at the end, therefore, appending a new element in each iteration would require walking through the entire list in each iteration. + +We can achieve the same result by prepending an element to the reversed list, and then reversing the result. Prepending is a fast operation and requires constant time. + +```haskell +-- Appending to the end of a list (potentially slow) +[1, 2, 3] ++ [4] ++ [5] ++ [6] + +-- Prepend to the start of a list (faster, due to the nature of linked lists) +6 : (5 : (4 : [3, 2, 1])) +-- then reverse! +``` + +There are several common Prelude functions for lists: + +- [`head`][head] returns the _head_ of a list -- the _first_ item in a list. +- [`tail`][tail] returns the _tail_ of the list -- the list _minus_ the _first_ item. +- [`length`][length] returns the number items in the list. +- [`elem`][in] returns a boolean value indicating whether the item is an element in the list. + +There is also the [`List` module][list]. + +Lists can only contain one data type. + +```haskell +list = [1, "string"] +-- Error: No instance for (Num String) arising from the literal ‘1’ +``` + +## Type annotation + +The type annotation of a list is `[a]` where a is the type which the lists holds, for example `String` or `Int`. + +``` haskell +a :: [Int] +a = [1, 2, 3] +``` + +[enum]: https://hexdocs.pm/elixir/Enum.html +[enum-protocol]: https://hexdocs.pm/elixir/Enumerable.html +[hd]: https://hexdocs.pm/elixir/Kernel.html#hd/1 +[in]: https://hexdocs.pm/elixir/Kernel.html#in/2 +[length]: https://hexdocs.pm/elixir/Kernel.html#length/1 +[list]: https://hexdocs.pm/elixir/List.html +[stream]: https://hexdocs.pm/elixir/Stream.html +[tl]: https://hexdocs.pm/elixir/Kernel.html#tl/1 +[linked-list-wiki]: https://en.wikipedia.org/wiki/Linked_list diff --git a/concepts/lists/links.json b/concepts/lists/links.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/concepts/lists/links.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/exercises/concept/language-list/.docs/hints.md b/exercises/concept/language-list/.docs/hints.md new file mode 100644 index 000000000..3e2c107b8 --- /dev/null +++ b/exercises/concept/language-list/.docs/hints.md @@ -0,0 +1,8 @@ +# General + +- Basic numbers operators are described in the Haskell [GHC.Num module documentation](https://hackage.haskell.org/package/base-4.16.0.0/docs/GHC-Num.html). But you might prefer a more easily digestable [basic introduction.](https://www.tutorialspoint.com/haskell/haskell_basic_operators.htm) + +# Modules and Indentation + +- [Declaring modules](https://learnyouahaskell.github.io/modules#making-our-own-modules) +- [Indentation rules](https://en.wikibooks.org/wiki/Haskell/Indentation) diff --git a/exercises/concept/language-list/.docs/instructions.md b/exercises/concept/language-list/.docs/instructions.md new file mode 100644 index 000000000..ddcdec7cd --- /dev/null +++ b/exercises/concept/language-list/.docs/instructions.md @@ -0,0 +1,66 @@ +# Instructions + +In this exercise you need to implement some functions to manipulate a list of programming languages. + +## 1. Define a function to return an empty language list + +Define the `new` function that takes no arguments and returns an empty list. + +```haskell +new +-- -> [] +``` + +## 2. Define a function to add a language to the list + +Define the `add/2` function that takes 2 arguments (a _language list_ and a string literal of a _language_). +It should return the resulting list with the new language prepended to the given list. + +```haskell +add new "Clojure" +-- -> ["Clojure"] +add ["Clojure"] "Haskell" +-- -> ["Haskell", "Clojure"] +``` + +## 3. Define a function to remove a language from the list + +Define the `remove` function that takes 1 argument (a _language list_). +It should return the list without the first item. Assume the list will always have at least one item. + +```haskell +remove ["Haskell", "Clojure", "Erlang"] +-- -> ["Clojure", "Erlang"] +``` + +## 4. Define a function to return the first item in the list + +Define the `first` function that takes 1 argument (a _language list_). +It should return the first language in the list. +Assume the list will always have at least one item. + +```haskell +first ["Elixir", "Haskell", "Clojure", "Prolog"] +-- -> "Elixir" +``` + +## 5. Define a function to return how many languages are in the list + +Define the `count` function that takes 1 argument (a _language list_). +It should return the number of languages in the list. + +```haskell +count ["Prolog", "Elm"] +-- -> 2 +``` + +## 6. Define a function to determine if the list includes a functional language + +Define the `isFunctionalList` function which takes 1 argument (a _language list_). +It should return a boolean value. +It should return `True` if _"Haskell"_ is one of the languages in the list. + +```haskell +isFunctionalList ["Haskell"] +-- -> True +``` diff --git a/exercises/concept/language-list/.docs/introduction.md b/exercises/concept/language-list/.docs/introduction.md new file mode 100644 index 000000000..bf76c86b8 --- /dev/null +++ b/exercises/concept/language-list/.docs/introduction.md @@ -0,0 +1,87 @@ +# About + +[Lists][list] are a basic data type in Haskell for holding a collection of values. +Lists are _immutable_, meaning they cannot be modified. +Any operation that changes a list returns a new list. +There are several methods in the prelude which allows you to work with Lists. + +Lists in Haskell are implemented as [linked lists][linked-list-wiki], and not as arrays of contiguous memory location. +Therefore, accessing an element in a list takes linear time depending on the length of the list. + +Lists can be written in literal form, head-tail notation, (which uses the `cons` operator `:`), or a combination of both: + +```haskell +-- Literal Form +[] +[1] +[1, 2, 3] + +-- Head-tail Notation +[] +-- same as [1] +1 : [] +-- same as [1, 2, 3] +1 : (2 : (3 : [])) + +-- Mixed +-- same as [1, 2, 3] +1 : [2, 3] +``` + +Head-tail notation can be used to append items to a list. + +```haskell +list = [2, 1] + +[3, 2, 1] == 3 : list +-- -> True +``` + +Appending elements to a list during iteration is considered an anti-pattern. +Appending an element requires walking through the entire list and adding the element at the end, therefore, appending a new element in each iteration would require walking through the entire list in each iteration. + +We can achieve the same result by prepending an element to the reversed list, and then reversing the result. Prepending is a fast operation and requires constant time. + +```haskell +-- Appending to the end of a list (potentially slow) +[1, 2, 3] ++ [4] ++ [5] ++ [6] + +-- Prepend to the start of a list (faster, due to the nature of linked lists) +6 : (5 : (4 : [3, 2, 1])) +-- then reverse! +``` + +There are several common Prelude functions for lists: + +- [`head`][head] returns the _head_ of a list -- the _first_ item in a list. +- [`tail`][tail] returns the _tail_ of the list -- the list _minus_ the _first_ item. +- [`length`][length] returns the number items in the list. +- [`elem`][in] returns a boolean value indicating whether the item is an element in the list. + +There is also the [`List` module][list]. + +Lists can only contain one data type. + +```haskell +list = [1, "string"] +-- Error: No instance for (Num String) arising from the literal ‘1’ +``` + +## Type annotation + +The type annotation of a list is `[a]` where a is the type which the lists holds, for example `String` or `Int`. + +``` haskell +a :: [Int] +a = [1, 2, 3] +``` + +[enum]: https://hexdocs.pm/elixir/Enum.html +[enum-protocol]: https://hexdocs.pm/elixir/Enumerable.html +[hd]: https://hexdocs.pm/elixir/Kernel.html#hd/1 +[in]: https://hexdocs.pm/elixir/Kernel.html#in/2 +[length]: https://hexdocs.pm/elixir/Kernel.html#length/1 +[list]: https://hexdocs.pm/elixir/List.html +[stream]: https://hexdocs.pm/elixir/Stream.html +[tl]: https://hexdocs.pm/elixir/Kernel.html#tl/1 +[linked-list-wiki]: https://en.wikipedia.org/wiki/Linked_list diff --git a/exercises/concept/language-list/.meta/STUB-ALLOWED-TO-NOT-COMPILE b/exercises/concept/language-list/.meta/STUB-ALLOWED-TO-NOT-COMPILE new file mode 100644 index 000000000..6d7c414d8 --- /dev/null +++ b/exercises/concept/language-list/.meta/STUB-ALLOWED-TO-NOT-COMPILE @@ -0,0 +1,3 @@ +Teaches the basics concept, wherein we want the students to learn how to define functions, +and we found that people usually learn best when they have to write things from scratch. +https://github.com/exercism/haskell/pull/1026#issuecomment-988556486 diff --git a/exercises/concept/language-list/.meta/config.json b/exercises/concept/language-list/.meta/config.json new file mode 100644 index 000000000..496ada26c --- /dev/null +++ b/exercises/concept/language-list/.meta/config.json @@ -0,0 +1,25 @@ +{ + "authors": [ + "meatball133" + ], + "files": { + "solution": [ + "src/LanguageList.hs", + "package.yaml" + ], + "test": [ + "test/Tests.hs" + ], + "exemplar": [ + ".meta/exemplar/src/LanguageListLanguageList.hs" + ], + "invalidator": [ + "stack.yaml" + ] + }, + "forked_from": [ + "elixir/language-list" + ], + "icon": "language-list", + "blurb": "Learn about lists by keeping track of the programming 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 000000000..5bbe978d3 --- /dev/null +++ b/exercises/concept/language-list/.meta/design.md @@ -0,0 +1,36 @@ +# Design + +## Learning objectives + +- Know of the existence of the `list` type. +- Know how list can be constructed with `[]` and `[ x : xs ]` syntax +- Use basic functions and macros related to Lists. + - `head` + - `tail` + - `length` + - `elem` +- Learn about string literals + +## Out of scope + +- Pattern matching +- `Enum` module functions / behaviors +- `String` functions and string manipulation +- Iterate over a list +- Memory and performance characteristics. + +## Concepts + +- `lists` + - know of the existence of the `list` type; + - know of the idea of `list` design; + - know some basic patterns / functions + - like `[]`, `[_:_]`, `head`, `tail`, `length/1`, `elem` +- `string-literals` + - not a standalone concept, captured in `basics` + - know how to write out a string with double quotes + +## Prerequisites + +- `booleans` + - know how to return booleans values from a function diff --git a/exercises/concept/language-list/.meta/exemplar/package.yaml b/exercises/concept/language-list/.meta/exemplar/package.yaml new file mode 100644 index 000000000..b725f6a1d --- /dev/null +++ b/exercises/concept/language-list/.meta/exemplar/package.yaml @@ -0,0 +1,18 @@ +name: language-list +version: 1.0.0.0 + +dependencies: + - base + +library: + exposed-modules: LanguageList + source-dirs: src + ghc-options: -Wall + +tests: + test: + main: Tests.hs + source-dirs: test + dependencies: + - language-list + - hspec diff --git a/exercises/concept/language-list/.meta/exemplar/src/LanguageList.hs b/exercises/concept/language-list/.meta/exemplar/src/LanguageList.hs new file mode 100644 index 000000000..dc5826f71 --- /dev/null +++ b/exercises/concept/language-list/.meta/exemplar/src/LanguageList.hs @@ -0,0 +1,19 @@ +module LanguageList (new, add, remove, first, count, isFunctionalList) where + +new :: [String] +new = [] + +add :: String -> [String] -> [String] +add language list = language : list + +remove :: [String] -> [String] +remove list = tail list + +first :: [String] -> String +first list = head list + +count :: [String] -> Int +count list = length list + +isFunctionalList :: [String] -> Bool +isFunctionalList list = "Haskell" `elem` list diff --git a/exercises/concept/language-list/package.yaml b/exercises/concept/language-list/package.yaml new file mode 100644 index 000000000..b725f6a1d --- /dev/null +++ b/exercises/concept/language-list/package.yaml @@ -0,0 +1,18 @@ +name: language-list +version: 1.0.0.0 + +dependencies: + - base + +library: + exposed-modules: LanguageList + source-dirs: src + ghc-options: -Wall + +tests: + test: + main: Tests.hs + source-dirs: test + dependencies: + - language-list + - hspec diff --git a/exercises/concept/language-list/src/LanguageList.hs b/exercises/concept/language-list/src/LanguageList.hs new file mode 100644 index 000000000..b720af213 --- /dev/null +++ b/exercises/concept/language-list/src/LanguageList.hs @@ -0,0 +1,25 @@ +module LanguageList (new, add, remove, first, count, isFunctionalList) where + +new :: [String] +new = + error "Implement this function." + +add :: String -> [String] -> [String] +add = + error "Implement this function." + +remove :: [String] -> [String] +remove = + error "Implement this function." + +first :: [String] -> String +first = + error "Implement this function." + +count :: [String] -> Int +count = + error "Implement this function." + +isFunctionalList :: [String] -> Bool +isFunctionalList = + error "Implement this function." diff --git a/exercises/concept/language-list/stack.yaml b/exercises/concept/language-list/stack.yaml new file mode 100644 index 000000000..115878212 --- /dev/null +++ b/exercises/concept/language-list/stack.yaml @@ -0,0 +1 @@ +resolver: lts-20.18 diff --git a/exercises/concept/language-list/test/Tests.hs b/exercises/concept/language-list/test/Tests.hs new file mode 100644 index 000000000..da0db80ab --- /dev/null +++ b/exercises/concept/language-list/test/Tests.hs @@ -0,0 +1,63 @@ +import Test.Hspec (it, shouldBe, hspec) +import LanguageList (new, add, remove, first, count, isFunctionalList) + +main :: IO () +main = hspec $ do + it "new list" $ do + new `shouldBe` [] + + it "add a language to a list" $ do + add "Haskell" new `shouldBe` ["Haskell"] + + it "add several languages to a list" $ do + let updatedList = add "Elixir" + $ add "F#" + $ add "Erlang" + $ add "Haskell" + $ add "Clojure" new + updatedList `shouldBe` ["Elixir", "F#", "Erlang", "Haskell", "Clojure"] + + it "add then remove results in empty list" $ do + let updatedList = remove + $ add "Haskell" new + updatedList `shouldBe` [] + + it "adding two languages, when removed, removes first item" $ do + let updatedList = remove + $ add "Elixir" + $ add "Haskell" new + updatedList `shouldBe` ["Haskell"] + + it "add one language, then get the first" $ do + let firstElement = first + $ add "Haskell" new + firstElement `shouldBe` "Haskell" + + it "add one language, then get the first" $ do + let firstElement = first + $ add "F#" + $ add "Erlang" + $ add "Elixir" + $ add "Haskell" new + firstElement `shouldBe` "F#" + + it "the count of a new list is 0" $ do + count new `shouldBe` 0 + + it "the count of a one-language list is 1" $ do + let countElements = count + $ add "Haskell" new + countElements `shouldBe` 1 + + it "the count of a multiple-item list is equal to its length" $ do + let countElements = count + $ add "Erlang" + $ add "Elixir" + $ add "Haskell" new + countElements `shouldBe` 3 + + it "a functional language list" $ do + isFunctionalList ["Clojure", "Haskell", "Erlang", "Elixir", "F#"] `shouldBe` True + + it "not a functional language list" $ do + isFunctionalList ["Java", "C", "JavaScript"] `shouldBe` False