From 108d26ab138433415a699b59ec92efcd623613e7 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Wed, 30 Oct 2019 08:54:30 +1100 Subject: [PATCH] update courses --- package.yaml | 2 + src/Course.hs | 1 + src/Course/Alternative.hs | 179 ++++++++++++++++++++++++++++++++++++ src/Course/Applicative.hs | 120 ++++++++++++------------ src/Course/Comonad.hs | 10 +- src/Course/Compose.hs | 14 ++- src/Course/Contravariant.hs | 123 +++++++++++++++++++++++++ src/Course/Core.hs | 3 +- src/Course/Extend.hs | 14 +-- src/Course/FastAnagrams.hs | 2 + src/Course/FileIO.hs | 6 +- src/Course/Functor.hs | 18 ++-- src/Course/JsonParser.hs | 12 ++- src/Course/List.hs | 6 +- src/Course/ListZipper.hs | 87 ++++++++++-------- src/Course/Monad.hs | 40 ++++---- src/Course/MoreParser.hs | 25 +++-- src/Course/Optional.hs | 17 +++- src/Course/Parser.hs | 157 ++++++++++++++++++++----------- src/Course/State.hs | 10 +- src/Course/StateT.hs | 151 +++++++++++++++++++----------- src/Course/Traversable.hs | 36 ++++---- src/Course/Validation.hs | 5 - 23 files changed, 746 insertions(+), 292 deletions(-) create mode 100644 src/Course/Alternative.hs create mode 100644 src/Course/Contravariant.hs diff --git a/package.yaml b/package.yaml index c6342526..8f42056d 100644 --- a/package.yaml +++ b/package.yaml @@ -46,11 +46,13 @@ library: exposed-modules: - Course + - Course.Alternative - Course.Anagrams - Course.Applicative - Course.Cheque - Course.Comonad - Course.Compose + - Course.Contravariant - Course.Core - Course.ExactlyOne - Course.Extend diff --git a/src/Course.hs b/src/Course.hs index 1e03d4a5..f2a27155 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 00000000..4b7504bc --- /dev/null +++ b/src/Course/Alternative.hs @@ -0,0 +1,179 @@ +{-# 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` +-- +-- 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. +-- 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 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 +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. +-- 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] +-- +-- >>> 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 +-- +-- /Note:/ In the standard library, this function is called @asum@ +aconcat :: Alternative k => List (k a) -> k a +aconcat = + error "todo: Course.Alternative aconcat" diff --git a/src/Course/Applicative.hs b/src/Course/Applicative.hs index 8cfed9f1..19ec6d5f 100644 --- a/src/Course/Applicative.hs +++ b/src/Course/Applicative.hs @@ -12,28 +12,28 @@ import Course.List import Course.Optional import qualified Prelude as P(fmap, return, (>>=)) --- | All instances of the `Applicative` type-class must satisfy three laws. +-- | All instances of the `Applicative` type-class must satisfy four laws. -- These laws are not checked by the compiler. These laws are given as: -- --- * The law of associative composition --- `∀a b c. ((.) <$> a <*> b <*> c) ≅ (a <*> (b <*> c))` --- -- * The law of identity --- `∀x. pure id <*> x ≅ x` +-- `∀x. pure id <*> x = x` +-- +-- * The law of composition +-- `∀u v w. pure (.) <*> u <*> v <*> w = u <*> (v <*> w)` -- -- * The law of homomorphism --- `∀f x. pure f <*> pure x ≅ pure (f x)` +-- `∀f x. pure f <*> pure x = pure (f x)` -- --- * The law of composition --- `∀u v w. pure (.) <*> u <*> v <*> w ≅ u <*> (v <*> w)` +-- * The law of interchange +-- `∀u y. u <*> pure y = pure ($ y) <*> u` -class Functor f => Applicative f where +class Functor k => Applicative k where pure :: - a -> f a + a -> k a (<*>) :: - f (a -> b) - -> f a - -> f b + k (a -> b) + -> k a + -> k b infixl 4 <*> @@ -49,7 +49,7 @@ instance Applicative ExactlyOne where -> ExactlyOne a pure = error "todo: Course.Applicative pure#instance ExactlyOne" - (<*>) :: + (<*>) :: ExactlyOne (a -> b) -> ExactlyOne a -> ExactlyOne b @@ -152,11 +152,11 @@ instance Applicative ((->) t) where -- >>> lift2 (+) length sum (listh [4,5,6]) -- 18 lift2 :: - Applicative f => + Applicative k => (a -> b -> c) - -> f a - -> f b - -> f c + -> k a + -> k b + -> k c lift2 = error "todo: Course.Applicative#lift2" @@ -184,12 +184,12 @@ lift2 = -- >>> lift3 (\a b c -> a + b + c) length sum product (listh [4,5,6]) -- 138 lift3 :: - Applicative f => + Applicative k => (a -> b -> c -> d) - -> f a - -> f b - -> f c - -> f d + -> k a + -> k b + -> k c + -> k d lift3 = error "todo: Course.Applicative#lift3" @@ -217,21 +217,21 @@ lift3 = -- >>> lift4 (\a b c d -> a + b + c + d) length sum product (sum . filter even) (listh [4,5,6]) -- 148 lift4 :: - Applicative f => + Applicative k => (a -> b -> c -> d -> e) - -> f a - -> f b - -> f c - -> f d - -> f e + -> k a + -> k b + -> k c + -> k d + -> k e lift4 = error "todo: Course.Applicative#lift4" -- | Apply a nullary function in the environment. lift0 :: - Applicative f => + Applicative k => a - -> f a + -> k a lift0 = error "todo: Course.Applicative#lift0" @@ -247,10 +247,10 @@ lift0 = -- >>> lift1 (+1) (1 :. 2 :. 3 :. Nil) -- [2,3,4] lift1 :: - Applicative f => + Applicative k => (a -> b) - -> f a - -> f b + -> k a + -> k b lift1 = error "todo: Course.Applicative#lift1" @@ -273,10 +273,10 @@ lift1 = -- -- prop> \x y -> Full x *> Full y == Full y (*>) :: - Applicative f => - f a - -> f b - -> f b + Applicative k => + k a + -> k b + -> k b (*>) = error "todo: Course.Applicative#(*>)" @@ -299,10 +299,10 @@ lift1 = -- -- prop> \x y -> Full x <* Full y == Full x (<*) :: - Applicative f => - f b - -> f a - -> f b + Applicative k => + k b + -> k a + -> k b (<*) = error "todo: Course.Applicative#(<*)" @@ -323,14 +323,16 @@ lift1 = -- >>> sequence ((*10) :. (+2) :. Nil) 6 -- [60,8] sequence :: - Applicative f => - List (f a) - -> f (List a) + Applicative k => + List (k a) + -> k (List a) sequence = error "todo: Course.Applicative#sequence" -- | Replicate an effect a given number of times. -- +-- /Tip:/ Use `Course.List#replicate`. +-- -- >>> replicateA 4 (ExactlyOne "hi") -- ExactlyOne ["hi","hi","hi","hi"] -- @@ -346,10 +348,10 @@ sequence = -- >>> replicateA 3 ('a' :. 'b' :. 'c' :. Nil) -- ["aaa","aab","aac","aba","abb","abc","aca","acb","acc","baa","bab","bac","bba","bbb","bbc","bca","bcb","bcc","caa","cab","cac","cba","cbb","cbc","cca","ccb","ccc"] replicateA :: - Applicative f => + Applicative k => Int - -> f a - -> f (List a) + -> k a + -> k (List a) replicateA = error "todo: Course.Applicative#replicateA" @@ -374,10 +376,10 @@ replicateA = -- [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]] -- filtering :: - Applicative f => - (a -> f Bool) + Applicative k => + (a -> k Bool) -> List a - -> f (List a) + -> k (List a) filtering = error "todo: Course.Applicative#filtering" @@ -392,23 +394,23 @@ instance Applicative IO where f P.>>= \f' -> P.fmap f' a return :: - Applicative f => + Applicative k => a - -> f a + -> k a return = pure fail :: - Applicative f => + Applicative k => Chars - -> f a + -> k a fail = error . hlist (>>) :: - Applicative f => - f a - -> f b - -> f b + Applicative k => + k a + -> k b + -> k b (>>) = (*>) diff --git a/src/Course/Comonad.hs b/src/Course/Comonad.hs index e74fca7f..85a776f6 100644 --- a/src/Course/Comonad.hs +++ b/src/Course/Comonad.hs @@ -16,9 +16,9 @@ import Course.Extend -- -- * The law of right identity -- `∀f. copure . (f <<=) == f -class Extend f => Comonad f where +class Extend k => Comonad k where copure :: - f a + k a -> a -- | Implement the @Comonad@ instance for @ExactlyOne@. @@ -37,9 +37,9 @@ instance Comonad ExactlyOne where -- >>> (+10) <$$> ExactlyOne 7 -- ExactlyOne 17 (<$$>) :: - Comonad f => + Comonad k => (a -> b) - -> f a - -> f b + -> k a + -> k b (<$$>) = error "todo: Course.Comonad#(<$>)" diff --git a/src/Course/Compose.hs b/src/Course/Compose.hs index bd8c9f9d..91319cb9 100644 --- a/src/Course/Compose.hs +++ b/src/Course/Compose.hs @@ -7,11 +7,12 @@ import Course.Core import Course.Functor import Course.Applicative import Course.Monad +import Course.Contravariant -- Exactly one of these exercises will not be possible to achieve. Determine which. newtype Compose f g a = - Compose (f (g a)) + Compose (f (g a)) deriving (Show, Eq) -- Implement a Functor instance for Compose instance (Functor f, Functor g) => @@ -32,4 +33,13 @@ instance (Monad f, Monad g) => Monad (Compose f g) where -- Implement the (=<<) function for a Monad instance for Compose (=<<) = - error "todo: Course.Compose (<<=)#instance (Compose f g)" + error "todo: Course.Compose (=<<)#instance (Compose f g)" + +-- Note that the inner g is Contravariant but the outer f is +-- Functor. We would not be able to write an instance if both were +-- Contravariant; why not? +instance (Functor f, Contravariant g) => + Contravariant (Compose f g) where +-- Implement the (>$<) function for a Contravariant instance for Compose + (>$<) = + error "todo: Course.Compose (>$<)#instance (Compose f g)" \ No newline at end of file diff --git a/src/Course/Contravariant.hs b/src/Course/Contravariant.hs new file mode 100644 index 00000000..0c4c6db6 --- /dev/null +++ b/src/Course/Contravariant.hs @@ -0,0 +1,123 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE InstanceSigs #-} + +module Course.Contravariant where + +import Course.Core + +-- | A 'Predicate' is usually some kind of test about a +-- thing. Example: a 'Predicate Integer' says "give me an 'Integer'" +-- and I'll answer 'True' or 'False'. +data Predicate a = Predicate (a -> Bool) + +runPredicate :: + Predicate a + -> a + -> Bool +runPredicate (Predicate f) = + f + +-- | A 'Comparison' looks at two things and says whether the first is +-- smaller, equal to, or larger than the second. 'Ordering' is a +-- three-valued type used as the result of a comparison, with +-- constructors 'LT', 'EQ', and 'GT'. +data Comparison a = Comparison (a -> a -> Ordering) + +runComparison :: + Comparison a + -> a + -> a + -> Ordering +runComparison (Comparison f) = + f + +-- | All this type does is swap the arguments around. We'll see why we +-- want it when we look at its 'Contravariant' instance. +data SwappedArrow a b = SwappedArrow (b -> a) + +runSwappedArrow :: + SwappedArrow a b + -> b + -> a +runSwappedArrow (SwappedArrow f) = f + +-- | All instances of the `Contravariant` type-class must satisfy two +-- laws. These laws are not checked by the compiler. These laws are +-- given as: +-- +-- * The law of identity +-- `∀x. (id >$< x) ≅ x` +-- +-- * The law of composition +-- `∀f g x. (g . f >$< x) ≅ (f >$< (g >$< x))` +-- +-- If you think of a 'Functor' as "having" an @a@ that you map over, +-- you can think of a 'Contravariant' as "accepting" an @a@. So if you +-- can turn @b@ into @a@ (i.e., with the first argument to (>$<)') +-- then you can make your 'Contravariant' accept @b@ instead. +class Contravariant k where + -- Pronounced, contramap. + (>$<) :: + (b -> a) + -> k a + -> k b + +infixl 4 >$< + +-- $setup +-- >>> :set -XOverloadedStrings +-- >>> import Course.Core +-- >>> import Prelude (length) + +-- | Maps a function before a Predicate. +-- +-- >>> runPredicate ((+1) >$< Predicate even) 2 +-- False +instance Contravariant Predicate where + (>$<) :: + (b -> a) + -> Predicate a + -> Predicate b + (>$<) = + error "todo: Course.Contravariant (>$<)#instance Predicate" + +-- | Use the function before comparing. +-- +-- >>> runComparison (show >$< Comparison compare) 2 12 +-- GT +instance Contravariant Comparison where + (>$<) :: + (b -> a) + -> Comparison a + -> Comparison b + (>$<) = + error "todo: Course.Contravariant (>$<)#instance Comparison" + +-- | The kind of the argument to 'Contravariant' is @Type -> Type@, so +-- our '(>$<)' only works on the final type argument. The +-- 'SwappedArrow' type reverses the arguments, which gives us the +-- right shape. +-- +-- >>> runSwappedArrow (length >$< SwappedArrow (+10)) "hello" +-- 15 +instance Contravariant (SwappedArrow t) where + (>$<) :: + (b -> a) + -> SwappedArrow x a + -> SwappedArrow x b + (>$<) = + error "todo: Course.Contravariant (>$<)#instance SwappedArrow" + + +-- | If we give our 'Contravariant' an @a@, then we can "accept" any +-- @b@ by ignoring it. +-- +-- prop> \x -> runPredicate (3 >$ Predicate odd) x == True +(>$) :: + Contravariant k => + a + -> k a + -> k b +(>$) = + error "todo: Course.Contravariant#(>$)" \ No newline at end of file diff --git a/src/Course/Core.hs b/src/Course/Core.hs index c5185c28..339b97ae 100644 --- a/src/Course/Core.hs +++ b/src/Course/Core.hs @@ -12,6 +12,7 @@ module Course.Core( , Fractional(..) , Bool(..) , Either(..) +, Ordering(..) , Int , Integer , IO @@ -57,6 +58,7 @@ import Prelude( , Fractional(..) , Bool(..) , Either(..) + , Ordering(..) , Char , Int , Integer @@ -118,4 +120,3 @@ bool f _ False = f bool _ t True = t - diff --git a/src/Course/Extend.hs b/src/Course/Extend.hs index 40b5be1e..6719b267 100644 --- a/src/Course/Extend.hs +++ b/src/Course/Extend.hs @@ -15,12 +15,12 @@ import Course.Functor -- -- * The law of associativity -- `∀f g. (f <<=) . (g <<=) ≅ (<<=) (f . (g <<=))` -class Functor f => Extend f where +class Functor k => Extend k where -- Pronounced, extend. (<<=) :: - (f a -> b) - -> f a - -> f b + (k a -> b) + -> k a + -> k b infixr 1 <<= @@ -83,8 +83,8 @@ instance Extend Optional where -- >>> cojoin Empty -- Empty cojoin :: - Extend f => - f a - -> f (f a) + Extend k => + k a + -> k (k a) cojoin = error "todo: Course.Extend#cojoin" diff --git a/src/Course/FastAnagrams.hs b/src/Course/FastAnagrams.hs index 09f133d2..ab6f3d61 100644 --- a/src/Course/FastAnagrams.hs +++ b/src/Course/FastAnagrams.hs @@ -10,6 +10,8 @@ import qualified Data.Set as S -- Return all anagrams of the given string -- that appear in the given dictionary file. +-- on a Mac - run this with: +-- > fastAnagrams "Tony" "/usr/share/dict/words" fastAnagrams :: Chars -> FilePath diff --git a/src/Course/FileIO.hs b/src/Course/FileIO.hs index d90fce91..85312588 100644 --- a/src/Course/FileIO.hs +++ b/src/Course/FileIO.hs @@ -39,7 +39,7 @@ Problem -- Consideration -- Try to avoid repetition. Factor out any common expressions. - + Example -- Given file files.txt, containing: a.txt @@ -61,7 +61,7 @@ To test this module, load ghci in the root of the project directory, and do Example output: $ ghci -GHCi, version ... +GHCi, version ... Loading package... Loading ... [ 1 of 28] Compiling (etc... @@ -113,7 +113,7 @@ getFiles = error "todo: Course.FileIO#getFiles" -- Given a file name, read it and for each line in that file, read and print contents of each. --- Use @getFiles@ and @printFiles@. +-- Use @getFiles@, @lines@, and @printFiles@. run :: FilePath -> IO () diff --git a/src/Course/Functor.hs b/src/Course/Functor.hs index 9ad73f5d..d50834fd 100644 --- a/src/Course/Functor.hs +++ b/src/Course/Functor.hs @@ -18,12 +18,12 @@ import qualified Prelude as P(fmap) -- -- * The law of composition -- `∀f g x.(f . g <$> x) ≅ (f <$> (g <$> x))` -class Functor f where +class Functor k where -- Pronounced, eff-map. (<$>) :: (a -> b) - -> f a - -> f b + -> k a + -> k b infixl 4 <$> @@ -95,10 +95,10 @@ instance Functor ((->) t) where -- -- prop> \x q -> x <$ Full q == Full x (<$) :: - Functor f => + Functor k => a - -> f b - -> f a + -> k b + -> k a (<$) = error "todo: Course.Functor#(<$)" @@ -116,9 +116,9 @@ instance Functor ((->) t) where -- >>> void (+10) 5 -- () void :: - Functor f => - f a - -> f () + Functor k => + k a + -> k () void = error "todo: Course.Functor#void" diff --git a/src/Course/JsonParser.hs b/src/Course/JsonParser.hs index 90b45173..ade8900a 100644 --- a/src/Course/JsonParser.hs +++ b/src/Course/JsonParser.hs @@ -79,7 +79,7 @@ toSpecialCharacter c = ('\\', Backslash) :. Nil in snd <$> find ((==) c . fst) table - + -- | Parse a JSON string. Handle double-quotes, special characters, hexadecimal characters. See http://json.org for the full list of control characters in JSON. -- -- /Tip:/ Use `hex`, `fromSpecialCharacter`, `between`, `is`, `charTok`, `toSpecialCharacter`. @@ -87,6 +87,9 @@ toSpecialCharacter c = -- >>> parse jsonString "\" abc\"" -- Result >< " abc" -- +-- >>> parse jsonString "\" abc\" " +-- Result >< " abc" +-- -- >>> parse jsonString "\"abc\"def" -- Result >def< "abc" -- @@ -116,9 +119,16 @@ jsonString = -- -- /Tip:/ Use @readFloats@. -- +-- /Optional:/ As an extra challenge, you may wish to support exponential notation +-- as defined on http://json.org/ +-- This is not required. +-- -- >>> parse jsonNumber "234" -- Result >< 234 % 1 -- +-- >>> parse jsonNumber "234 " +-- Result >< 234 % 1 +-- -- >>> parse jsonNumber "-234" -- Result >< (-234) % 1 -- diff --git a/src/Course/List.hs b/src/Course/List.hs index fdffc661..4dfe16cc 100644 --- a/src/Course/List.hs +++ b/src/Course/List.hs @@ -40,7 +40,7 @@ data List t = infixr 5 :. instance Show t => Show (List t) where - show = show . foldRight (:) [] + show = show . hlist -- The list of integers from zero to infinity. infinity :: @@ -219,7 +219,7 @@ flattenAgain = -- | Convert a list of optional values to an optional list of values. -- --- * If the list contains all `Full` values, +-- * If the list contains all `Full` values, -- then return `Full` list of values. -- -- * If the list contains one or more `Empty` values, @@ -324,7 +324,7 @@ produce f x = x :. produce f (f x) -- >>> notReverse Nil -- [] -- --- prop> \x -> let types = x :: List Int in notReverse x ++ notReverse y == notReverse (y ++ x) +-- prop> \x y -> let types = x :: List Int in notReverse x ++ notReverse y == notReverse (y ++ x) -- -- prop> \x -> let types = x :: Int in notReverse (x :. Nil) == x :. Nil notReverse :: diff --git a/src/Course/ListZipper.hs b/src/Course/ListZipper.hs index 1caecaae..6091d5b5 100644 --- a/src/Course/ListZipper.hs +++ b/src/Course/ListZipper.hs @@ -51,14 +51,19 @@ rights (ListZipper _ _ r) = -- A `MaybeListZipper` is a data structure that allows us to "fail" zipper operations. -- e.g. Moving left when there are no values to the left. --- --- We then overload operations polymorphically to operate on both `ListZipper` and `MaybeListZipper` --- using the `ListZipper'` type-class below. -data MaybeListZipper a = - IsZ (ListZipper a) - | IsNotZ +newtype MaybeListZipper a = + MLZ (Optional (ListZipper a)) deriving Eq +isZ :: + ListZipper a + -> MaybeListZipper a +isZ = MLZ . Full + +isNotZ :: + MaybeListZipper a +isNotZ = MLZ Empty + -- | Implement the `Functor` instance for `ListZipper`. -- -- >>> (+1) <$> (zipper [3,2,1] 4 [5,6,7]) @@ -69,7 +74,7 @@ instance Functor ListZipper where -- | Implement the `Functor` instance for `MaybeListZipper`. -- --- >>> (+1) <$> (IsZ (zipper [3,2,1] 4 [5,6,7])) +-- >>> (+1) <$> (MLZ (Full (zipper [3,2,1] 4 [5,6,7]))) -- [4,3,2] >5< [6,7,8] instance Functor MaybeListZipper where (<$>) = @@ -95,9 +100,9 @@ toList = toListZ :: MaybeListZipper a -> List a -toListZ IsNotZ = +toListZ (MLZ Empty) = Nil -toListZ (IsZ z) = +toListZ (MLZ (Full z)) = toList z -- | Create a `MaybeListZipper` positioning the focus at the head. @@ -137,17 +142,15 @@ zipper l x r = fromOptional :: Optional (ListZipper a) -> MaybeListZipper a -fromOptional Empty = - IsNotZ -fromOptional (Full z) = - IsZ z +fromOptional = + MLZ asZipper :: (ListZipper a -> ListZipper a) -> MaybeListZipper a -> MaybeListZipper a asZipper f = - asMaybeZipper (IsZ . f) + asMaybeZipper (isZ . f) (>$>):: (ListZipper a -> ListZipper a) @@ -160,9 +163,9 @@ asMaybeZipper :: (ListZipper a -> MaybeListZipper a) -> MaybeListZipper a -> MaybeListZipper a -asMaybeZipper _ IsNotZ = - IsNotZ -asMaybeZipper f (IsZ z) = +asMaybeZipper _ (MLZ Empty) = + isNotZ +asMaybeZipper f (MLZ (Full z)) = f z (-<<) :: @@ -237,12 +240,12 @@ hasRight :: hasRight = error "todo: Course.ListZipper#hasRight" --- | Seek to the left for a location matching a predicate, starting from the --- current one. +-- | Seek to the left for a location matching a predicate, excluding the +-- focus. -- -- /Tip:/ Use `break` -- --- prop> \xs p -> findLeft (const p) -<< fromList xs == IsNotZ +-- prop> \xs p -> findLeft (const p) -<< fromList xs == isNotZ -- -- >>> findLeft (== 1) (zipper [2, 1] 3 [4, 5]) -- [] >1< [2,3,4,5] @@ -264,13 +267,13 @@ findLeft :: -> MaybeListZipper a findLeft = error "todo: Course.ListZipper#findLeft" - --- | Seek to the right for a location matching a predicate, starting from the --- current one. + +-- | Seek to the right for a location matching a predicate, excluding the +-- focus. -- -- /Tip:/ Use `break` -- --- prop> \xs -> findRight (const False) -<< fromList xs == IsNotZ +-- prop> \xs -> findRight (const False) -<< fromList xs == isNotZ -- -- >>> findRight (== 5) (zipper [2, 1] 3 [4, 5]) -- [4,3,2,1] >5< [] @@ -449,6 +452,10 @@ moveRightN = -- -- >>> moveLeftN' (-4) (zipper [5,4,3,2,1] 6 [7,8,9]) -- Left 3 +-- +-- >>> rights <$> moveLeftN' 1 (zipper [3,2,error "moveLeftN' not sufficiently lazy"] 4 [5,6,7]) +-- Right [4,5,6,7] +-- moveLeftN' :: Int -> ListZipper a @@ -502,7 +509,7 @@ nth = -- >>> index (zipper [3,2,1] 4 [5,6,7]) -- 3 -- --- prop> \i z z' -> optional True (\z' -> index z' == i) (toOptional (nth i z)) +-- prop> \i z -> optional True (\z' -> index z' == i) (toOptional (nth i z)) index :: ListZipper a -> Int @@ -618,20 +625,20 @@ instance Applicative ListZipper where -- /Tip:/ Use @pure@ for `ListZipper`. -- /Tip:/ Use `<*>` for `ListZipper`. -- --- prop> \z n -> let is (IsZ z) = z in all . (==) <*> take n . lefts . is . pure +-- prop> \z n -> let is (MLZ (Full z)) = z in all . (==) <*> take n . lefts . is . pure -- --- prop> \z n -> let is (IsZ z) = z in all . (==) <*> take n . rights . is . pure +-- prop> \z n -> let is (MLZ (Full z)) = z in all . (==) <*> take n . rights . is . pure -- --- >>> IsZ (zipper [(+2), (+10)] (*2) [(*3), (4*), (5+)]) <*> IsZ (zipper [3,2,1] 4 [5,6,7]) +-- >>> isZ (zipper [(+2), (+10)] (*2) [(*3), (4*), (5+)]) <*> isZ (zipper [3,2,1] 4 [5,6,7]) -- [5,12] >8< [15,24,12] -- --- >>> IsNotZ <*> IsZ (zipper [3,2,1] 4 [5,6,7]) +-- >>> isNotZ <*> isZ (zipper [3,2,1] 4 [5,6,7]) -- >< -- --- >>> IsZ (zipper [(+2), (+10)] (*2) [(*3), (4*), (5+)]) <*> IsNotZ +-- >>> isZ (zipper [(+2), (+10)] (*2) [(*3), (4*), (5+)]) <*> isNotZ -- >< -- --- >>> IsNotZ <*> IsNotZ +-- >>> isNotZ <*> isNotZ -- >< instance Applicative MaybeListZipper where pure = @@ -654,10 +661,10 @@ instance Extend ListZipper where -- This instance will use the `Extend` instance for `ListZipper`. -- -- --- id <<= IsNotZ +-- id <<= isNotZ -- >< -- --- >>> id <<= (IsZ (zipper [2,1] 3 [4,5])) +-- >>> id <<= (isZ (zipper [2,1] 3 [4,5])) -- [[1] >2< [3,4,5],[] >1< [2,3,4,5]] >[2,1] >3< [4,5]< [[3,2,1] >4< [5],[4,3,2,1] >5< []] instance Extend MaybeListZipper where (<<=) = @@ -673,7 +680,8 @@ instance Comonad ListZipper where error "todo: Course.ListZipper copure#instance ListZipper" -- | Implement the `Traversable` instance for `ListZipper`. --- This implementation traverses a zipper while running some `Applicative` effect through the zipper. +-- This implementation traverses a zipper from left to right while running +-- some `Applicative` effect through the zipper. -- An effectful zipper is returned. -- -- >>> traverse id (zipper [Full 1, Full 2, Full 3] (Full 4) [Full 5, Full 6, Full 7]) @@ -681,6 +689,9 @@ instance Comonad ListZipper where -- -- >>> traverse id (zipper [Full 1, Full 2, Full 3] (Full 4) [Empty, Full 6, Full 7]) -- Empty +-- +-- >>> traverse id (zipper [error "traversing left values in wrong order", Empty] (error "traversing focus before left values") [Full 5, Full 6, Full 7]) +-- Empty instance Traversable ListZipper where traverse = error "todo: Course.ListZipper traverse#instance ListZipper" @@ -689,10 +700,10 @@ instance Traversable ListZipper where -- -- /Tip:/ Use `traverse` for `ListZipper`. -- --- >>> traverse id IsNotZ +-- >>> traverse id isNotZ -- >< -- --- >>> traverse id (IsZ (zipper [Full 1, Full 2, Full 3] (Full 4) [Full 5, Full 6, Full 7])) +-- >>> traverse id (isZ (zipper [Full 1, Full 2, Full 3] (Full 4) [Full 5, Full 6, Full 7])) -- Full [1,2,3] >4< [5,6,7] instance Traversable MaybeListZipper where traverse = @@ -707,5 +718,5 @@ instance Show a => Show (ListZipper a) where stringconcat [show l, " >", show x, "< ", show r] instance Show a => Show (MaybeListZipper a) where - show (IsZ z) = show z - show IsNotZ = "><" + show (MLZ (Full z)) = show z + show (MLZ Empty) = "><" diff --git a/src/Course/Monad.hs b/src/Course/Monad.hs index e637d9a3..96df997f 100644 --- a/src/Course/Monad.hs +++ b/src/Course/Monad.hs @@ -18,12 +18,12 @@ import qualified Prelude as P((=<<)) -- -- * The law of associativity -- `∀f g x. g =<< (f =<< x) ≅ ((g =<<) . f) =<< x` -class Applicative f => Monad f where +class Applicative k => Monad k where -- Pronounced, bind. (=<<) :: - (a -> f b) - -> f a - -> f b + (a -> k b) + -> k a + -> k b infixr 1 =<< @@ -107,10 +107,10 @@ instance Monad ((->) t) where -- >>> ((*) <**> (+2)) 3 -- 15 (<**>) :: - Monad f => - f (a -> b) - -> f a - -> f b + Monad k => + k (a -> b) + -> k a + -> k b (<**>) = error "todo: Course.Monad#(<**>)" @@ -130,9 +130,9 @@ infixl 4 <**> -- >>> join (+) 7 -- 14 join :: - Monad f => - f (f a) - -> f a + Monad k => + k (k a) + -> k a join = error "todo: Course.Monad#join" @@ -143,26 +143,26 @@ join = -- >>> ((+10) >>= (*)) 7 -- 119 (>>=) :: - Monad f => - f a - -> (a -> f b) - -> f b + Monad k => + k a + -> (a -> k b) + -> k b (>>=) = error "todo: Course.Monad#(>>=)" infixl 1 >>= -- | Implement composition within the @Monad@ environment. --- Pronounced, kleisli composition. +-- Pronounced, Kleisli composition. -- -- >>> ((\n -> n :. n :. Nil) <=< (\n -> n+1 :. n+2 :. Nil)) 1 -- [2,2,3,3] (<=<) :: - Monad f => - (b -> f c) - -> (a -> f b) + Monad k => + (b -> k c) + -> (a -> k b) -> a - -> f c + -> k c (<=<) = error "todo: Course.Monad#(<=<)" diff --git a/src/Course/MoreParser.hs b/src/Course/MoreParser.hs index 2e39a0fc..1d50263d 100644 --- a/src/Course/MoreParser.hs +++ b/src/Course/MoreParser.hs @@ -1,6 +1,7 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE RebindableSyntax #-} +{-# LANGUAGE OverloadedStrings #-} module Course.MoreParser where @@ -12,6 +13,7 @@ import Course.Applicative import Course.Monad import Course.Functor import Course.Traversable +import Numeric hiding (readHex) -- $setup -- >>> :set -XOverloadedStrings @@ -44,7 +46,7 @@ spaces = -- | Write a function that applies the given parser, then parses 0 or more spaces, -- then produces the result of the original parser. -- --- /Tip:/ Use the monad instance. +-- /Tip:/ Use the applicative instance or the monad instance. -- -- >>> parse (tok (is 'a')) "a bc" -- Result >bc< 'a' @@ -64,7 +66,7 @@ tok = -- -- >>> isErrorResult (parse (charTok 'a') "dabc") -- True --- +-- -- /Tip:/ Use `tok` and `is`. charTok :: Char @@ -79,7 +81,7 @@ charTok = -- -- >>> isErrorResult( parse commaTok "1,23") -- True --- +-- -- /Tip:/ Use `charTok`. commaTok :: Parser Char @@ -116,9 +118,9 @@ string :: Chars -> Parser Chars string = - error "todo: Course.MoreParser#is" + error "todo: Course.MoreParser#string" --- | Write a function that parsers the given string, followed by 0 or more spaces. +-- | Write a function that parses the given string, followed by 0 or more spaces. -- -- /Tip:/ Use `tok` and `string`. -- @@ -196,7 +198,7 @@ noneof = -- | Write a function that applies the first parser, runs the third parser keeping the result, -- then runs the second parser and produces the obtained result. -- --- /Tip:/ Use the monad instance. +-- /Tip:/ Use the applicative instance or the monad instance -- -- >>> parse (between (is '[') (is ']') character) "[a]" -- Result >< 'a' @@ -242,7 +244,7 @@ betweenCharTok = -- | Write a function that parses 4 hex digits and return the character value. -- --- /Tip:/ Use `readHex`, `isHexDigit`, `replicateA`, `satisfy` and the monad instance. +-- /Tip:/ Use `readHex`, `isHexDigit`, `replicateA`, `satisfy`, `chr` and the monad instance. -- -- >>> parse hex "0010" -- Result >< '\DLE' @@ -396,6 +398,15 @@ satisfyAny = -- >>> parse (betweenSepbyComma '[' ']' lower) "[]" -- Result >< "" -- +-- >>> parse (betweenSepbyComma '[' ']' lower) "[a,b,c]" +-- Result >< "abc" +-- +-- >>> parse (betweenSepbyComma '[' ']' lower) "[a, b, c]" +-- Result >< "abc" +-- +-- >>> parse (betweenSepbyComma '[' ']' digits1) "[123,456]" +-- Result >< ["123","456"] +-- -- >>> isErrorResult (parse (betweenSepbyComma '[' ']' lower) "[A]") -- True -- diff --git a/src/Course/Optional.hs b/src/Course/Optional.hs index 033c328e..4a38e90c 100644 --- a/src/Course/Optional.hs +++ b/src/Course/Optional.hs @@ -80,7 +80,22 @@ bindOptional = -> Optional a -> Optional a (<+>) = - error "todo: Course.Optional#(<+>)" + error "todo: Course.Optional#(<+>)" + +-- | Replaces the Full and Empty constructors in an optional. +-- +-- >>> optional (+1) 0 (Full 8) +-- 9 +-- +-- >>> optional (+1) 0 Empty +-- 0 +optional :: + (a -> b) + -> b + -> Optional a + -> b +optional = + error "todo: Course.Optional#optional" applyOptional :: Optional (a -> b) -> Optional a -> Optional b applyOptional f a = bindOptional (\f' -> mapOptional f' a) f diff --git a/src/Course/Parser.hs b/src/Course/Parser.hs index 4a5773e4..3b44dba1 100644 --- a/src/Course/Parser.hs +++ b/src/Course/Parser.hs @@ -40,7 +40,7 @@ instance Show a => Show (ParseResult a) where stringconcat ["Unexpected string: ", show s] show (Result i a) = stringconcat ["Result >", hlist i, "< ", show a] - + instance Functor ParseResult where _ <$> UnexpectedEof = UnexpectedEof @@ -53,7 +53,7 @@ instance Functor ParseResult where f <$> Result i a = Result i (f a) --- Function to determine is a parse result is an error. +-- Function to determine whether this @ParseResult@ is an error. isErrorResult :: ParseResult a -> Bool @@ -73,15 +73,15 @@ onResult :: ParseResult a -> (Input -> a -> ParseResult b) -> ParseResult b -onResult UnexpectedEof _ = +onResult UnexpectedEof _ = UnexpectedEof -onResult (ExpectedEof i) _ = +onResult (ExpectedEof i) _ = ExpectedEof i -onResult (UnexpectedChar c) _ = +onResult (UnexpectedChar c) _ = UnexpectedChar c -onResult (UnexpectedString s) _ = +onResult (UnexpectedString s) _ = UnexpectedString s -onResult (Result i a) k = +onResult (Result i a) k = k i a data Parser a = P (Input -> ParseResult a) @@ -216,52 +216,6 @@ instance Applicative Parser where (<*>) = error "todo: Course.Parser (<*>)#instance Parser" --- | Return a parser that continues producing a list of values from the given parser. --- --- /Tip:/ Use @list1@, @pure@ and @(|||)@. --- --- >>> parse (list character) "" --- Result >< "" --- --- >>> parse (list digit) "123abc" --- Result >abc< "123" --- --- >>> parse (list digit) "abc" --- Result >abc< "" --- --- >>> parse (list character) "abc" --- Result >< "abc" --- --- >>> parse (list (character *> valueParser 'v')) "abc" --- Result >< "vvv" --- --- >>> parse (list (character *> valueParser 'v')) "" --- Result >< "" -list :: - Parser a - -> Parser (List a) -list = - error "todo: Course.Parser#list" - --- | Return a parser that produces at least one value from the given parser then --- continues producing a list of values from the given parser (to ultimately produce a non-empty list). --- --- /Tip:/ Use @(=<<)@, @list@ and @pure@. --- --- >>> parse (list1 (character)) "abc" --- Result >< "abc" --- --- >>> parse (list1 (character *> valueParser 'v')) "abc" --- Result >< "vvv" --- --- >>> isErrorResult (parse (list1 (character *> valueParser 'v')) "") --- True -list1 :: - Parser a - -> Parser (List a) -list1 = - error "todo: Course.Parser#list1" - -- | Return a parser that produces a character but fails if -- -- * The input is empty. @@ -300,6 +254,18 @@ is = -- * The produced character is not a digit. -- -- /Tip:/ Use the @satisfy@ and @Data.Char#isDigit@ functions. +-- +-- >>> parse digit "9" +-- Result >< '9' +-- +-- >>> parse digit "123" +-- Result >23< '1' +-- +-- >>> isErrorResult (parse digit "") +-- True +-- +-- >>> isErrorResult (parse digit "hello") +-- True digit :: Parser Char digit = @@ -313,11 +279,88 @@ digit = -- * The produced character is not a space. -- -- /Tip:/ Use the @satisfy@ and @Data.Char#isSpace@ functions. +-- +-- >>> parse space " " +-- Result >< ' ' +-- +-- >>> parse space "\n z" +-- Result > z< '\n' +-- +-- >>> isErrorResult (parse space "") +-- True +-- +-- >>> isErrorResult (parse space "a") +-- True space :: Parser Char space = error "todo: Course.Parser#space" +-- | Return a parser that conses the result of the first parser onto the result of +-- the second. Pronounced "cons parser". +-- +-- /Tip:/ Use @lift2@ +-- +-- >>> parse (character .:. valueParser Nil) "abc" +-- Result >bc< "a" +-- +-- >>> parse (digit .:. valueParser "hello") "321" +-- Result >21< "3hello" +(.:.) :: + Parser a + -> Parser (List a) + -> Parser (List a) +(.:.) = + error "todo: Course.Parser#(.:.)" + +infixr 5 .:. + +-- | Return a parser that continues producing a list of values from the given parser. +-- +-- /Tip:/ Use @list1@, @pure@ and @(|||)@. +-- +-- >>> parse (list character) "" +-- Result >< "" +-- +-- >>> parse (list digit) "123abc" +-- Result >abc< "123" +-- +-- >>> parse (list digit) "abc" +-- Result >abc< "" +-- +-- >>> parse (list character) "abc" +-- Result >< "abc" +-- +-- >>> parse (list (character *> valueParser 'v')) "abc" +-- Result >< "vvv" +-- +-- >>> parse (list (character *> valueParser 'v')) "" +-- Result >< "" +list :: + Parser a + -> Parser (List a) +list = + error "todo: Course.Parser#list" + +-- | Return a parser that produces at least one value from the given parser then +-- continues producing a list of values from the given parser (to ultimately produce a non-empty list). +-- +-- /Tip:/ Use @(=<<)@, @list@ and @pure@. +-- +-- >>> parse (list1 (character)) "abc" +-- Result >< "abc" +-- +-- >>> parse (list1 (character *> valueParser 'v')) "abc" +-- Result >< "vvv" +-- +-- >>> isErrorResult (parse (list1 (character *> valueParser 'v')) "") +-- True +list1 :: + Parser a + -> Parser (List a) +list1 = + error "todo: Course.Parser#list1" + -- | Return a parser that produces one or more space characters -- (consuming until the first non-space) but fails if -- @@ -521,9 +564,9 @@ phoneParser = -- | Write a parser for Person. -- --- /Tip:/ Use @(=<<)@, +-- /Tip:/ Use @(>>=)@, -- @pure@, --- @(>>>)@, +-- @(*>)@, -- @spaces1@, -- @ageParser@, -- @firstNameParser@, @@ -531,6 +574,10 @@ phoneParser = -- @smokerParser@, -- @phoneParser@. -- +-- /Tip:/ Follow-on exercise: Use *(<*>)* instead of @(>>=)@. +-- +-- /Tip:/ Follow-on exercise: Use *(<*>~)* instead of @(<*>)@ and @(*>)@. +-- -- >>> isErrorResult (parse personParser "") -- True -- diff --git a/src/Course/State.hs b/src/Course/State.hs index 91e9287d..898b1d3b 100644 --- a/src/Course/State.hs +++ b/src/Course/State.hs @@ -90,8 +90,7 @@ instance Functor (State s) where -- >>> runState (pure (+1) <*> pure 0) 0 -- (1,0) -- --- >>> import qualified Prelude as P --- >>> runState (State (\s -> ((+3), s P.++ ["apple"])) <*> State (\s -> (7, s P.++ ["banana"]))) [] +-- >>> runState (State (\s -> ((+3), s ++ ("apple":.Nil))) <*> State (\s -> (7, s ++ ("banana":.Nil)))) Nil -- (10,["apple","banana"]) instance Applicative (State s) where pure :: @@ -102,17 +101,20 @@ instance Applicative (State s) where (<*>) :: State s (a -> b) -> State s a - -> State s b + -> State s b (<*>) = error "todo: Course.State (<*>)#instance (State s)" --- | Implement the `Bind` instance for `State s`. +-- | Implement the `Monad` instance for `State s`. -- -- >>> runState ((const $ put 2) =<< put 1) 0 -- ((),2) -- -- >>> let modify f = State (\s -> ((), f s)) in runState (modify (+1) >>= \() -> modify (*2)) 7 -- ((),16) +-- +-- >>> runState ((\a -> State (\s -> (a + s, 10 + s))) =<< State (\s -> (s * 2, 4 + s))) 2 +-- (10,16) instance Monad (State s) where (=<<) :: (a -> State s b) diff --git a/src/Course/StateT.hs b/src/Course/StateT.hs index cbc4047e..7da3f51d 100644 --- a/src/Course/StateT.hs +++ b/src/Course/StateT.hs @@ -22,27 +22,27 @@ import qualified Prelude as P -- >>> import qualified Prelude as P(fmap) -- >>> instance Arbitrary a => Arbitrary (List a) where arbitrary = P.fmap listh arbitrary --- | A `StateT` is a function from a state value `s` to a functor f of (a produced value `a`, and a resulting state `s`). -newtype StateT s f a = +-- | A `StateT` is a function from a state value `s` to a functor k of (a produced value `a`, and a resulting state `s`). +newtype StateT s k a = StateT { runStateT :: s - -> f (a, s) + -> k (a, s) } --- | Implement the `Functor` instance for @StateT s f@ given a @Functor f@. +-- | Implement the `Functor` instance for @StateT s k@ given a @Functor k@. -- -- >>> runStateT ((+1) <$> (pure 2) :: StateT Int List Int) 0 -- [(3,0)] -instance Functor f => Functor (StateT s f) where +instance Functor k => Functor (StateT s k) where (<$>) :: (a -> b) - -> StateT s f a - -> StateT s f b + -> StateT s k a + -> StateT s k b (<$>) = - error "todo: Course.StateT (<$>)#instance (StateT s f)" + error "todo: Course.StateT (<$>)#instance (StateT s k)" --- | Implement the `Applicative` instance for @StateT s f@ given a @Monad f@. +-- | Implement the `Applicative` instance for @StateT s k@ given a @Monad k@. -- -- >>> runStateT (pure 2) 0 -- (2,0) @@ -53,26 +53,25 @@ instance Functor f => Functor (StateT s f) where -- >>> runStateT (pure (+2) <*> ((pure 2) :: StateT Int List Int)) 0 -- [(4,0)] -- --- >>> import qualified Prelude as P --- >>> runStateT (StateT (\s -> Full ((+2), s P.++ [1])) <*> (StateT (\s -> Full (2, s P.++ [2])))) [0] +-- >>> runStateT (StateT (\s -> Full ((+2), s ++ (1:.Nil))) <*> (StateT (\s -> Full (2, s ++ (2:.Nil))))) (0:.Nil) -- Full (4,[0,1,2]) -- --- >>> runStateT (StateT (\s -> ((+2), s P.++ [1]) :. ((+3), s P.++ [1]) :. Nil) <*> (StateT (\s -> (2, s P.++ [2]) :. Nil))) [0] +-- >>> runStateT (StateT (\s -> ((+2), s ++ (1:.Nil)) :. ((+3), s ++ (1:.Nil)) :. Nil) <*> (StateT (\s -> (2, s ++ (2:.Nil)) :. Nil))) (0:.Nil) -- [(4,[0,1,2]),(5,[0,1,2])] -instance Monad f => Applicative (StateT s f) where +instance Monad k => Applicative (StateT s k) where pure :: a - -> StateT s f a + -> StateT s k a pure = - error "todo: Course.StateT pure#instance (StateT s f)" + error "todo: Course.StateT pure#instance (StateT s k)" (<*>) :: - StateT s f (a -> b) - -> StateT s f a - -> StateT s f b + StateT s k (a -> b) + -> StateT s k a + -> StateT s k b (<*>) = - error "todo: Course.StateT (<*>)#instance (StateT s f)" + error "todo: Course.StateT (<*>)#instance (StateT s k)" --- | Implement the `Monad` instance for @StateT s f@ given a @Monad f@. +-- | Implement the `Monad` instance for @StateT s k@ given a @Monad k@. -- Make sure the state value is passed through in `bind`. -- -- >>> runStateT ((const $ putT 2) =<< putT 1) 0 @@ -80,13 +79,13 @@ instance Monad f => Applicative (StateT s f) where -- -- >>> let modify f = StateT (\s -> pure ((), f s)) in runStateT (modify (+1) >>= \() -> modify (*2)) 7 -- ((),16) -instance Monad f => Monad (StateT s f) where +instance Monad k => Monad (StateT s k) where (=<<) :: - (a -> StateT s f b) - -> StateT s f a - -> StateT s f b + (a -> StateT s k b) + -> StateT s k a + -> StateT s k b (=<<) = - error "todo: Course.StateT (=<<)#instance (StateT s f)" + error "todo: Course.StateT (=<<)#instance (StateT s k)" -- | A `State'` is `StateT` specialised to the `ExactlyOne` functor. type State' s a = @@ -95,7 +94,7 @@ type State' s a = -- | Provide a constructor for `State'` values -- -- >>> runStateT (state' $ runState $ put 1) 0 --- ExactlyOne ((),1) +-- ExactlyOne ((),1) state' :: (s -> (a, s)) -> State' s a @@ -114,15 +113,21 @@ runState' = error "todo: Course.StateT#runState'" -- | Run the `StateT` seeded with `s` and retrieve the resulting state. +-- +-- >>> execT (StateT $ \s -> Full ((), s + 1)) 2 +-- Full 3 execT :: - Functor f => - StateT s f a + Functor k => + StateT s k a -> s - -> f s + -> k s execT = error "todo: Course.StateT#execT" --- | Run the `State` seeded with `s` and retrieve the resulting state. +-- | Run the `State'` seeded with `s` and retrieve the resulting state. +-- +-- >>> exec' (state' $ \s -> ((), s + 1)) 2 +-- 3 exec' :: State' s a -> s @@ -131,15 +136,21 @@ exec' = error "todo: Course.StateT#exec'" -- | Run the `StateT` seeded with `s` and retrieve the resulting value. +-- +-- >>> evalT (StateT $ \s -> Full (even s, s + 1)) 2 +-- Full True evalT :: - Functor f => - StateT s f a + Functor k => + StateT s k a -> s - -> f a + -> k a evalT = error "todo: Course.StateT#evalT" --- | Run the `State` seeded with `s` and retrieve the resulting value. +-- | Run the `State'` seeded with `s` and retrieve the resulting value. +-- +-- >>> eval' (state' $ \s -> (even s, s + 1)) 5 +-- False eval' :: State' s a -> s @@ -152,8 +163,8 @@ eval' = -- >>> (runStateT (getT :: StateT Int List Int) 3) -- [(3,3)] getT :: - Applicative f => - StateT s f s + Applicative k => + StateT s k s getT = error "todo: Course.StateT#getT" @@ -165,9 +176,9 @@ getT = -- >>> runStateT (putT 2 :: StateT Int List ()) 0 -- [((),2)] putT :: - Applicative f => + Applicative k => s - -> StateT s f () + -> StateT s k () putT = error "todo: Course.StateT#putT" @@ -177,7 +188,7 @@ putT = -- -- prop> \xs -> distinct' xs == distinct' (flatMap (\x -> x :. x :. Nil) xs) distinct' :: - (Ord a, Num a) => + Ord a => List a -> List a distinct' = @@ -202,21 +213,25 @@ distinctF = error "todo: Course.StateT#distinctF" -- | An `OptionalT` is a functor of an `Optional` value. -data OptionalT f a = +data OptionalT k a = OptionalT { runOptionalT :: - f (Optional a) + k (Optional a) } --- | Implement the `Functor` instance for `OptionalT f` given a Functor f. +-- | Implement the `Functor` instance for `OptionalT k` given a Functor k. -- -- >>> runOptionalT $ (+1) <$> OptionalT (Full 1 :. Empty :. Nil) -- [Full 2,Empty] -instance Functor f => Functor (OptionalT f) where +instance Functor k => Functor (OptionalT k) where + (<$>) :: + (a -> b) + -> OptionalT k a + -> OptionalT k b (<$>) = - error "todo: Course.StateT (<$>)#instance (OptionalT f)" + error "todo: Course.StateT (<$>)#instance (OptionalT k)" --- | Implement the `Applicative` instance for `OptionalT f` given a Monad f. +-- | Implement the `Applicative` instance for `OptionalT k` given a Monad k. -- -- /Tip:/ Use `onFull` to help implement (<*>). -- @@ -240,19 +255,31 @@ instance Functor f => Functor (OptionalT f) where -- -- >>> runOptionalT $ OptionalT (Full (+1) :. Full (+2) :. Nil) <*> OptionalT (Full 1 :. Empty :. Nil) -- [Full 2,Empty,Full 3,Empty] -instance Monad f => Applicative (OptionalT f) where +instance Monad k => Applicative (OptionalT k) where + pure :: + a + -> OptionalT k a pure = - error "todo: Course.StateT pure#instance (OptionalT f)" + error "todo: Course.StateT pure#instance (OptionalT k)" + + (<*>) :: + OptionalT k (a -> b) + -> OptionalT k a + -> OptionalT k b (<*>) = - error "todo: Course.StateT (<*>)#instance (OptionalT f)" + error "todo: Course.StateT (<*>)#instance (OptionalT k)" --- | Implement the `Monad` instance for `OptionalT f` given a Monad f. +-- | Implement the `Monad` instance for `OptionalT k` given a Monad k. -- -- >>> runOptionalT $ (\a -> OptionalT (Full (a+1) :. Full (a+2) :. Nil)) =<< OptionalT (Full 1 :. Empty :. Nil) -- [Full 2,Full 3,Empty] -instance Monad f => Monad (OptionalT f) where +instance Monad k => Monad (OptionalT k) where + (=<<) :: + (a -> OptionalT k b) + -> OptionalT k a + -> OptionalT k b (=<<) = - error "todo: Course.StateT (=<<)#instance (OptionalT f)" + error "todo: Course.StateT (=<<)#instance (OptionalT k)" -- | A `Logger` is a pair of a list of log values (`[l]`) and an arbitrary value (`a`). data Logger l a = @@ -264,6 +291,10 @@ data Logger l a = -- >>> (+3) <$> Logger (listh [1,2]) 3 -- Logger [1,2] 6 instance Functor (Logger l) where + (<$>) :: + (a -> b) + -> Logger l a + -> Logger l b (<$>) = error "todo: Course.StateT (<$>)#instance (Logger l)" @@ -275,8 +306,16 @@ instance Functor (Logger l) where -- >>> Logger (listh [1,2]) (+7) <*> Logger (listh [3,4]) 3 -- Logger [1,2,3,4] 10 instance Applicative (Logger l) where + pure :: + a + -> Logger l a pure = error "todo: Course.StateT pure#instance (Logger l)" + + (<*>) :: + Logger l (a -> b) + -> Logger l a + -> Logger l b (<*>) = error "todo: Course.StateT (<*>)#instance (Logger l)" @@ -286,6 +325,10 @@ instance Applicative (Logger l) where -- >>> (\a -> Logger (listh [4,5]) (a+3)) =<< Logger (listh [1,2]) 3 -- Logger [1,2,4,5] 6 instance Monad (Logger l) where + (=<<) :: + (a -> Logger l b) + -> Logger l a + -> Logger l b (=<<) = error "todo: Course.StateT (=<<)#instance (Logger l)" @@ -322,10 +365,10 @@ distinctG = error "todo: Course.StateT#distinctG" onFull :: - Applicative f => - (t -> f (Optional a)) + Applicative k => + (t -> k (Optional a)) -> Optional t - -> f (Optional a) + -> k (Optional a) onFull g o = case o of Empty -> diff --git a/src/Course/Traversable.hs b/src/Course/Traversable.hs index 9cdf3262..58634717 100644 --- a/src/Course/Traversable.hs +++ b/src/Course/Traversable.hs @@ -12,7 +12,7 @@ import Course.ExactlyOne import Course.Optional import Course.Compose --- | All instances of the `Traversable` type-class must satisfy two laws. These +-- | All instances of the `Traversable` type-class must satisfy three laws. These -- laws are not checked by the compiler. These laws are given as: -- -- * The law of naturality @@ -25,35 +25,35 @@ import Course.Compose -- `∀f g. traverse ((g <$>) . f) ≅ (traverse g <$>) . traverse f` class Functor t => Traversable t where traverse :: - Applicative f => - (a -> f b) + Applicative k => + (a -> k b) -> t a - -> f (t b) + -> k (t b) instance Traversable List where traverse :: - Applicative f => - (a -> f b) + Applicative k => + (a -> k b) -> List a - -> f (List b) + -> k (List b) traverse f = foldRight (\a b -> (:.) <$> f a <*> b) (pure Nil) instance Traversable ExactlyOne where traverse :: - Applicative f => - (a -> f b) + Applicative k => + (a -> k b) -> ExactlyOne a - -> f (ExactlyOne b) + -> k (ExactlyOne b) traverse = error "todo: Course.Traversable traverse#instance ExactlyOne" instance Traversable Optional where traverse :: - Applicative f => - (a -> f b) + Applicative k => + (a -> k b) -> Optional a - -> f (Optional b) + -> k (Optional b) traverse = error "todo: Course.Traversable traverse#instance Optional" @@ -68,9 +68,9 @@ instance Traversable Optional where -- >>> sequenceA (Full (*10)) 6 -- Full 60 sequenceA :: - (Applicative f, Traversable t) => - t (f a) - -> f (t a) + (Applicative k, Traversable t) => + t (k a) + -> k (t a) sequenceA = error "todo: Course.Traversable#sequenceA" @@ -82,7 +82,7 @@ instance (Traversable f, Traversable g) => -- | The `Product` data type contains one value from each of the two type constructors. data Product f g a = - Product (f a) (g a) + Product (f a) (g a) deriving (Show, Eq) instance (Functor f, Functor g) => Functor (Product f g) where @@ -99,7 +99,7 @@ instance (Traversable f, Traversable g) => -- | The `Coproduct` data type contains one value from either of the two type constructors. data Coproduct f g a = InL (f a) - | InR (g a) + | InR (g a) deriving (Show, Eq) instance (Functor f, Functor g) => Functor (Coproduct f g) where diff --git a/src/Course/Validation.hs b/src/Course/Validation.hs index 932664bd..de43d13d 100644 --- a/src/Course/Validation.hs +++ b/src/Course/Validation.hs @@ -6,11 +6,6 @@ module Course.Validation where import qualified Prelude as P(String) import Course.Core --- class Validation { --- Validation(String error) {} // Error --- Validation(A value) {} // Value --- } - -- $setup -- >>> import Test.QuickCheck -- >>> import qualified Prelude as P(fmap, either)