Created
April 9, 2021 06:34
-
-
Save hyperrealgopher/2990e00193f65a56009fac37cec0372c to your computer and use it in GitHub Desktop.
system-f/fp-course: State.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE NoImplicitPrelude #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
{-# LANGUAGE InstanceSigs #-} | |
{-# LANGUAGE RebindableSyntax #-} | |
module Course.State where | |
import Course.Core | |
import qualified Prelude as P | |
import Course.Optional | |
import Course.List | |
import Course.Functor | |
import Course.Applicative | |
import Course.Monad | |
import qualified Data.Set as S | |
-- $setup | |
-- >>> import Test.QuickCheck.Function | |
-- >>> import Data.List(nub) | |
-- >>> import Test.QuickCheck | |
-- >>> import qualified Prelude as P(fmap) | |
-- >>> import Course.Core | |
-- >>> import Course.List | |
-- >>> instance Arbitrary a => Arbitrary (List a) where arbitrary = P.fmap listh arbitrary | |
-- A `State` is a function from a state value `s` to (a produced value `a`, and a resulting state `s`). | |
newtype State s a = | |
State { | |
runState :: | |
s | |
-> (a, s) | |
} | |
-- | Run the `State` seeded with `s` and retrieve the resulting state. | |
-- | |
-- prop> \(Fun _ f) s -> exec (State f) s == snd (runState (State f) s) | |
exec :: | |
State s a | |
-> s | |
-> s | |
exec st s = snd $ runState st s | |
-- | Run the `State` seeded with `s` and retrieve the resulting value. | |
-- | |
-- prop> \(Fun _ f) s -> eval (State f) s == fst (runState (State f) s) | |
eval :: | |
State s a | |
-> s | |
-> a | |
eval st s = fst $ runState st s | |
-- | A `State` where the state also distributes into the produced value. | |
-- | |
-- >>> runState get 0 | |
-- (0,0) | |
get :: | |
State s s | |
get = State (\x -> (x, x)) | |
-- | A `State` where the resulting state is seeded with the given value. | |
-- | |
-- >>> runState (put 1) 0 | |
-- ((),1) | |
put :: | |
s | |
-> State s () | |
put x = State (const ((), x)) | |
-- | Implement the `Functor` instance for `State s`. | |
-- | |
-- >>> runState ((+1) <$> State (\s -> (9, s * 2))) 3 | |
-- (10,6) | |
instance Functor (State s) where | |
(<$>) :: | |
(a -> b) | |
-> State s a | |
-> State s b | |
(<$>) f st = | |
State sf | |
where | |
sf x = | |
let stf = runState st | |
in (\x -> (f . fst $ x, snd x)) <$> stf $ x | |
-- | Implement the `Applicative` instance for `State s`. | |
-- | |
-- >>> runState (pure 2) 0 | |
-- (2,0) | |
-- | |
-- >>> runState (pure (+1) <*> pure 0) 0 | |
-- (1,0) | |
-- | |
-- >>> runState (State (\s -> ((+3), s ++ ("apple":.Nil))) <*> State (\s -> (7, s ++ ("banana":.Nil)))) Nil | |
-- (10,["apple","banana"]) | |
instance Applicative (State s) where | |
pure :: | |
a | |
-> State s a | |
pure a = State (\x -> (a, x)) | |
(<*>) :: | |
State s (a -> b) | |
-> State s a | |
-> State s b | |
(<*>) st1 st2 = State $ \x -> | |
let (st1f1, st1f2) = runState st1 x | |
(st2v, st2f) = runState st2 st1f2 | |
in (st1f1 st2v, st2f) | |
-- | 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) | |
-> State s a | |
-> State s b | |
(=<<) f st = State $ \x -> | |
let (a, s) = runState st x | |
in runState (f a) s | |
-- | Find the first element in a `List` that satisfies a given predicate. | |
-- It is possible that no element is found, hence an `Optional` result. | |
-- However, while performing the search, we sequence some `Monad` effect through. | |
-- | |
-- Note the similarity of the type signature to List#find | |
-- where the effect appears in every return position: | |
-- find :: (a -> Bool) -> List a -> Optional a | |
-- findM :: (a -> f Bool) -> List a -> f (Optional a) | |
-- | |
-- >>> let p x = (\s -> (const $ pure (x == 'c')) =<< put (1+s)) =<< get in runState (findM p $ listh ['a'..'h']) 0 | |
-- (Full 'c',3) | |
-- | |
-- >>> let p x = (\s -> (const $ pure (x == 'i')) =<< put (1+s)) =<< get in runState (findM p $ listh ['a'..'h']) 0 | |
-- (Empty,8) | |
findM :: | |
Monad f => | |
(a -> f Bool) | |
-> List a | |
-> f (Optional a) | |
findM predicate (l :. ls) = (\x -> if x then pure (Full l) else findM predicate ls) =<< predicate l | |
findM predicate Nil = pure Empty | |
-- | Find the first element in a `List` that repeats. | |
-- It is possible that no element repeats, hence an `Optional` result. | |
-- | |
-- /Tip:/ Use `findM` and `State` with a @Data.Set#Set@. | |
-- | |
-- prop> \xs -> case firstRepeat xs of Empty -> let xs' = hlist xs in nub xs' == xs'; Full x -> length (filter (== x) xs) > 1 | |
-- prop> \xs -> case firstRepeat xs of Empty -> True; Full x -> let (l, (rx :. rs)) = span (/= x) xs in let (l2, r2) = span (/= x) rs in let l3 = hlist (l ++ (rx :. Nil) ++ l2) in nub l3 == l3 | |
firstRepeat :: | |
Ord a => | |
List a | |
-> Optional a | |
firstRepeat list = genericForFirstRepeatAndDistinct findM list True False | |
genericForFirstRepeatAndDistinct action list result1 result2 = | |
fst $ runState (action predicate list) (S.fromList []) | |
where | |
predicate e = do | |
seenSet <- get | |
if e `S.member` seenSet | |
then pure result1 | |
else put (S.insert e seenSet) >> pure result2 | |
-- | Remove all duplicate elements in a `List`. | |
-- /Tip:/ Use `filtering` and `State` with a @Data.Set#Set@. | |
-- | |
-- prop> \xs -> firstRepeat (distinct xs) == Empty | |
-- | |
-- prop> \xs -> distinct xs == distinct (flatMap (\x -> x :. x :. Nil) xs) | |
distinct :: | |
Ord a => | |
List a | |
-> List a | |
distinct list = genericForFirstRepeatAndDistinct filtering list False True | |
-- | A happy number is a positive integer, where the sum of the square of its digits eventually reaches 1 after repetition. | |
-- In contrast, a sad number (not a happy number) is where the sum of the square of its digits never reaches 1 | |
-- because it results in a recurring sequence. | |
-- | |
-- /Tip:/ Use `firstRepeat` with `produce`. | |
-- | |
-- /Tip:/ Use `join` to write a @square@ function. | |
-- | |
-- /Tip:/ Use library functions: @Optional#contains@, @Data.Char#digitToInt@. | |
-- | |
-- >>> isHappy 4 | |
-- False | |
-- | |
-- >>> isHappy 7 | |
-- True | |
-- | |
-- >>> isHappy 42 | |
-- False | |
-- | |
-- >>> isHappy 44 | |
-- True | |
isHappy :: | |
Integer | |
-> Bool | |
isHappy x = | |
let result number = foldRight (\x acc -> let y = (P.toInteger $ digitToInt x) in (y*y) + acc) 0 (listh $ show number) | |
in Full 1 == (firstRepeat $ produce result x) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment