From 5ee63b819c0fe06c74e815962a6e2409179f2ab3 Mon Sep 17 00:00:00 2001 From: George Wilson Date: Mon, 29 Jul 2019 14:58:28 +1000 Subject: [PATCH 1/5] Add module on Alternative closes #330 --- course.cabal | 1 + src/Course.hs | 1 + src/Course/Alternative.hs | 164 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/Course/Alternative.hs diff --git a/course.cabal b/course.cabal index 357fe5f7c..fb20e18be 100644 --- a/course.cabal +++ b/course.cabal @@ -50,6 +50,7 @@ library hs-source-dirs: src exposed-modules: Course + Course.Alternative Course.Anagrams Course.Applicative Course.Cheque diff --git a/src/Course.hs b/src/Course.hs index 1e03d4a5f..f2a27155f 100644 --- a/src/Course.hs +++ b/src/Course.hs @@ -3,6 +3,7 @@ module Course (module X) where +import Course.Alternative as X import Course.Anagrams as X import Course.Applicative as X import Course.Cheque as X diff --git a/src/Course/Alternative.hs b/src/Course/Alternative.hs new file mode 100644 index 000000000..ce82b4bab --- /dev/null +++ b/src/Course/Alternative.hs @@ -0,0 +1,164 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE RebindableSyntax #-} + +module Course.Alternative where + +import Course.Applicative +import Course.Core +import Course.Functor +import Course.List +import Course.Optional +import Course.Parser +import qualified Prelude as P(fmap, return, (>>=)) + +-- | All instances of the `Alternative` type-class must satisfy three laws. +-- These laws are not checked by the compiler. These laws are given as: +-- +-- * The law of left identity +-- `∀x. empty <|> x = x` +-- +-- * The law of right identity +-- `∀x. x <|> empty = x` +-- +-- * The law of associativity +-- `∀u v w. u <|> (v <|> w) = (u <|> v) <|> w` + +class Applicative k => Alternative k where + zero :: + k a + (<|>) :: + k a + -> k a + -> k a + +infixl 3 <|> + +-- | Return the first full Optional +-- +-- >>> Full 3 <|> zero +-- Full 3 +-- +-- >>> zero <|> Full 4 +-- Full 4 +-- +-- >>> Full 3 <|> Full 4 +-- Full 3 +instance Alternative Optional where + zero :: + Optional a + zero = + error "todo: Course.Alternative zero#instance Optional" + (<|>) :: + Optional a + -> Optional a + -> Optional a + (<|>) = + error "todo: Course.Alternative (<|>)#instance Optional" + +-- | Append the lists +-- +-- >>> 3 :. 4 :. 5 :. Nil <|> Nil +-- [3,4,5] +-- +-- >>> Nil <|> 6 :. 7 :. 8 :. Nil +-- [6,7,8] +-- +-- >>> 3 :. 4 :. 5 :. Nil <|> 6 :. 7 :. 8 :. Nil +-- [3,4,5,6,7,8] +instance Alternative List where + zero :: + List a + zero = + error "todo: Course.Alternative zero#instance List" + (<|>) :: + List a + -> List a + -> List a + (<|>) = + error "todo: Course.Alternative (<|>)#instance List" + +-- | Choose the first succeeding parser +-- +-- /Tip:/ Check Parser.hs +-- +-- >>> parse (character ||| valueParser 'v') "" +-- Result >< 'v' +-- +-- >>> parse (constantParser UnexpectedEof ||| valueParser 'v') "" +-- Result >< 'v' +-- +-- >>> parse (character ||| valueParser 'v') "abc" +-- Result >bc< 'a' +-- +-- >>> parse (constantParser UnexpectedEof ||| valueParser 'v') "abc" +-- Result >abc< 'v' +instance Alternative Parser where + zero :: + Parser a + zero = + error "todo: Course.Alternative zero#instance Parser" + (<|>) :: + Parser a + -> Parser a + -> Parser a + (<|>) = + error "todo: Course.Alternative (<|>)#instance Parser" + +-- | Run the provided Alternative action zero or more times, collecting +-- a list of the results. +-- +-- /Tip:/ Use @some@, @pure@ and @(<|>)@. +-- +-- >>> parse (many character) "" +-- Result >< "" +-- +-- >>> parse (many digit) "123abc" +-- Result >abc< "123" +-- +-- >>> parse (many digit) "abc" +-- Result >abc< "" +-- +-- >>> parse (many character) "abc" +-- Result >< "abc" +-- +-- >>> parse (many (character *> valueParser 'v')) "abc" +-- Result >< "vvv" +-- +-- >>> parse (many (character *> valueParser 'v')) "" +-- Result >< "" +many :: Alternative k => k a -> k (List a) +many = + error "todo: Course.Alternative many" + +-- | Run the provided Alternative action one or more times, collecting +-- a list of the results. +-- +-- /Tip:/ Use @(:.)@ and @many@. +-- +-- >>> parse (some (character)) "abc" +-- Result >< "abc" +-- +-- >>> parse (some (character *> valueParser 'v')) "abc" +-- Result >< "vvv" +-- +-- >>> isErrorResult (parse (some (character *> valueParser 'v')) "") +-- True +some :: Alternative k => k a -> k (List a) +some = + error "todo: Course.Alternative some" + +-- | Combine a list of alternatives +-- +-- >>> aconcat (Nil :: List (List Int)) +-- [] +-- +-- >>> aconcat ((3:.4:.Nil) :. Nil :. (5:.6:.Nil) :. Nil +-- [3,4,5,6] + +-- >>> aconcat (Empty :. Empty :. Full 7 :. Empty :. Full 8 :. Empty :. Nil) +-- Full 7 +aconcat :: Alternative k => List (k a) -> k a +aconcat = + error "todo: Course.Alternative aconcat" From eb281df83a9f80beb90185d27ff5d47b9de0ad41 Mon Sep 17 00:00:00 2001 From: George Wilson Date: Mon, 29 Jul 2019 15:24:57 +1000 Subject: [PATCH 2/5] incorporate feedback into Alternative module --- src/Course/Alternative.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Course/Alternative.hs b/src/Course/Alternative.hs index ce82b4bab..05381de18 100644 --- a/src/Course/Alternative.hs +++ b/src/Course/Alternative.hs @@ -24,7 +24,8 @@ import qualified Prelude as P(fmap, return, (>>=)) -- -- * The law of associativity -- `∀u v w. u <|> (v <|> w) = (u <|> v) <|> w` - +-- +-- You may notice that these are the same laws as Monoid. class Applicative k => Alternative k where zero :: k a @@ -83,16 +84,16 @@ instance Alternative List where -- -- /Tip:/ Check Parser.hs -- --- >>> parse (character ||| valueParser 'v') "" +-- >>> parse (character <|> valueParser 'v') "" -- Result >< 'v' -- --- >>> parse (constantParser UnexpectedEof ||| valueParser 'v') "" +-- >>> parse (constantParser UnexpectedEof <|> valueParser 'v') "" -- Result >< 'v' -- --- >>> parse (character ||| valueParser 'v') "abc" +-- >>> parse (character <|> valueParser 'v') "abc" -- Result >bc< 'a' -- --- >>> parse (constantParser UnexpectedEof ||| valueParser 'v') "abc" +-- >>> parse (constantParser UnexpectedEof <|> valueParser 'v') "abc" -- Result >abc< 'v' instance Alternative Parser where zero :: From 64296dbfbe8e21390d5a3e004430a1e13ea0f97b Mon Sep 17 00:00:00 2001 From: George Wilson Date: Mon, 29 Jul 2019 16:47:27 +1000 Subject: [PATCH 3/5] Address more feedback --- src/Course/Alternative.hs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Course/Alternative.hs b/src/Course/Alternative.hs index 05381de18..cafc39549 100644 --- a/src/Course/Alternative.hs +++ b/src/Course/Alternative.hs @@ -25,7 +25,12 @@ import qualified Prelude as P(fmap, return, (>>=)) -- * The law of associativity -- `∀u v w. u <|> (v <|> w) = (u <|> v) <|> w` -- --- You may notice that these are the same laws as Monoid. +-- You may notice that these are the same laws as Monoid. An alternative +-- can be considered a "monoid on applicative functors". The key difference +-- between the two classes is that Alternative is higher-kinded, meaning that +-- the type variable @k@ itself takes a type parameter. +-- It is common for a type to have different instances for Monoid and +-- Alternative. class Applicative k => Alternative k where zero :: k a @@ -160,6 +165,8 @@ some = -- >>> aconcat (Empty :. Empty :. Full 7 :. Empty :. Full 8 :. Empty :. Nil) -- Full 7 +-- +-- /Note:/ In the standard library, this function is called @asum@ aconcat :: Alternative k => List (k a) -> k a aconcat = error "todo: Course.Alternative aconcat" From 1e9665269a8324d4d466aacd8bea3d40ce44ce5c Mon Sep 17 00:00:00 2001 From: George Wilson Date: Tue, 30 Jul 2019 13:40:27 +1000 Subject: [PATCH 4/5] Discuss relationship to Applicative --- src/Course/Alternative.hs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Course/Alternative.hs b/src/Course/Alternative.hs index cafc39549..55ff5c023 100644 --- a/src/Course/Alternative.hs +++ b/src/Course/Alternative.hs @@ -29,8 +29,13 @@ import qualified Prelude as P(fmap, return, (>>=)) -- can be considered a "monoid on applicative functors". The key difference -- between the two classes is that Alternative is higher-kinded, meaning that -- the type variable @k@ itself takes a type parameter. --- It is common for a type to have different instances for Monoid and --- Alternative. +-- The Alternative instance for @k@ is often distinct from any Monoid instance +-- for @k a@. +-- An Alternative instance should relate to the Applicative instance in some +-- way, although this is not a firmly resolved question in the community. +-- Informally, it should be some kind of choice or alternation. Attempts to give +-- laws relating the Applicative and Alternative are discussed here: +-- https://wiki.haskell.org/Typeclassopedia#Laws_6 class Applicative k => Alternative k where zero :: k a From 23b6eb203a625edc8ad0ebcfd5cc7e77b9b7c562 Mon Sep 17 00:00:00 2001 From: George Wilson Date: Tue, 30 Jul 2019 14:25:02 +1000 Subject: [PATCH 5/5] Fix the last of the pull request comments --- src/Course/Alternative.hs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Course/Alternative.hs b/src/Course/Alternative.hs index 55ff5c023..4b7504bca 100644 --- a/src/Course/Alternative.hs +++ b/src/Course/Alternative.hs @@ -32,7 +32,7 @@ import qualified Prelude as P(fmap, return, (>>=)) -- The Alternative instance for @k@ is often distinct from any Monoid instance -- for @k a@. -- An Alternative instance should relate to the Applicative instance in some --- way, although this is not a firmly resolved question in the community. +-- way, although the exact relation required is an open question in the community. -- Informally, it should be some kind of choice or alternation. Attempts to give -- laws relating the Applicative and Alternative are discussed here: -- https://wiki.haskell.org/Typeclassopedia#Laws_6 @@ -46,7 +46,7 @@ class Applicative k => Alternative k where infixl 3 <|> --- | Return the first full Optional +-- | Return the first full Optional. -- -- >>> Full 3 <|> zero -- Full 3 @@ -68,7 +68,9 @@ instance Alternative Optional where (<|>) = error "todo: Course.Alternative (<|>)#instance Optional" --- | Append the lists +-- | Append the lists. +-- This instance views lists as a non-deterministic choice between elements, +-- so the way we "alternate" them is to append the lists. -- -- >>> 3 :. 4 :. 5 :. Nil <|> Nil -- [3,4,5]