-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathReader.hs
132 lines (109 loc) · 4.27 KB
/
Reader.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
-- | Example uses and instances of the @HasReader@ capability.
module Reader where
import Capability.Reader
import Capability.Source
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (ReaderT (..))
import Data.Kind (Type)
import GHC.Generics (Generic)
import Test.Common
import Test.Hspec
data Foo
data Bar
type instance TypeOf Type Foo = Int
type instance TypeOf Type Bar = Int
----------------------------------------------------------------------
-- Example Programs
-- | Returns the triple of the number in context "foo".
tripleFoo :: HasReader' Foo m => m Int
tripleFoo = do
single <- ask @Foo
double <- asks @Foo (*2)
pure $ single + double
-- | Prints the triple and sixfold of the number in context "foo".
fooExample :: (HasReader' Foo m, MonadIO m) => m ()
fooExample = do
liftIO . print =<< tripleFoo
liftIO . print =<< local @Foo (*2) tripleFoo
-- | Prints the double of "bar" and the triple of "foo".
fooBarExample
:: (HasReader' Foo m, HasReader' Bar m, MonadIO m) => m ()
fooBarExample = do
local @Bar (*2) $ do
liftIO . print =<< ask @Bar
liftIO . print =<< tripleFoo
-- | Shows the interaction between 'local' and 'magnify'.
fooBarMagnify
:: (HasReader "foobar" FooBar m, MonadIO m) => m ()
fooBarMagnify = do
magnify
@"foo"
@(Field "foo" "foobar")
@('[HasReader "foobar" FooBar, MonadIO]) $ do
FooBar a b <- local @"foo" (const 5) (ask @"foobar")
c <- local @"foobar" (const $ FooBar 3 4) (ask @"foo")
liftIO $ print ((a, b), c)
----------------------------------------------------------------------
-- Instances
-- | @HasReader@ instance derived via @MonadReader@.
newtype FooReaderT m (a :: Type) = FooReaderT (ReaderT Int m a)
deriving (Functor, Applicative, Monad, MonadIO)
deriving (HasSource Foo Int) via MonadReader (ReaderT Int m)
deriving (HasReader Foo Int) via MonadReader (ReaderT Int m)
runFooReaderT :: FooReaderT m a -> m a
runFooReaderT (FooReaderT m) = runReaderT m 1
data FooBar = FooBar
{ foo :: Int
, bar :: Int
} deriving Generic
-- | Multiple @HasReader@ instances derived via record fields in @MonadReader@.
newtype FooBarReader a = FooBarReader (ReaderT FooBar IO a)
deriving (Functor, Applicative, Monad, MonadIO)
deriving (HasSource "foobar" FooBar, HasReader "foobar" FooBar) via
(MonadReader (ReaderT FooBar IO))
deriving (HasSource Foo Int, HasReader Foo Int) via
Rename "foo" (Field "foo" () (MonadReader (ReaderT FooBar IO)))
deriving (HasSource Bar Int, HasReader Bar Int) via
Rename "bar" (Field "bar" () (MonadReader (ReaderT FooBar IO)))
runFooBarReader :: FooBarReader a -> IO a
runFooBarReader (FooBarReader m) = runReaderT m FooBar { foo = 1, bar = 2 }
-- | Multiple @HasReader@ instances on the same underlying @MonadReader@.
--
-- Demonstrates colliding instances.
-- Note, do not do this in practice. The derived @HasReader@ instances interact
-- in unexpected ways.
newtype BadFooBarReader a = BadFooBarReader (ReaderT Int IO a)
deriving (Functor, Applicative, Monad, MonadIO)
deriving (HasSource Foo Int, HasReader Foo Int)
via MonadReader (ReaderT Int IO)
deriving (HasSource Bar Int, HasReader Bar Int)
via MonadReader (ReaderT Int IO)
runBadFooBarReader :: BadFooBarReader a -> IO a
runBadFooBarReader (BadFooBarReader m) = runReaderT m 1
----------------------------------------------------------------------
-- Test Cases
spec :: Spec
spec = do
describe "FooReaderT" $
it "evaluates fooExample" $
runFooReaderT fooExample `shouldPrint` "3\n6\n"
describe "FooBarReader" $ do
it "evaluates fooExample" $
runFooBarReader fooExample `shouldPrint` "3\n6\n"
it "evaluates fooBarExample" $
runFooBarReader fooBarExample `shouldPrint` "4\n3\n"
it "evaluates fooBarMagnify" $
runFooBarReader fooBarMagnify `shouldPrint` "((5,2),3)\n"
describe "BadFooBarReader" $ do
it "evaluates fooExample" $
runBadFooBarReader fooExample `shouldPrint` "3\n6\n"
it "evaluates fooBarExample" $
runBadFooBarReader fooBarExample `shouldNotPrint` "4\n3\n"