Skip to content

Instantly share code, notes, and snippets.

@hyperrealgopher
Created April 9, 2021 06:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hyperrealgopher/2990e00193f65a56009fac37cec0372c to your computer and use it in GitHub Desktop.
Save hyperrealgopher/2990e00193f65a56009fac37cec0372c to your computer and use it in GitHub Desktop.
system-f/fp-course: State.hs
{-# 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