-
Notifications
You must be signed in to change notification settings - Fork 0
/
Part3b.hs
229 lines (185 loc) · 7.11 KB
/
Part3b.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
-- Note: We need `UndecidableInstances` to only derive `Eq`. We *know* it will be safe.
-- | This is essentially `Part3b` with additional methods
module HelHUG.Part3b where
------------------------------------------------------------------------
-- Library code
--
import Control.Applicative
import Control.Monad.Error (throwError)
import Data.List (sort, intercalate)
import Data.Traversable
import qualified HelHUG.DB as DB (entityId)
import HelHUG.DB hiding (entityId)
import HelHUG.DB.Attribute
-- | Peano numbers.
data Nat = Zero | Succ Nat
-- | Singleton type for `Nat`
data SNat :: Nat -> * where
SZero :: SNat 'Zero
SSucc :: forall (n :: Nat). SNat n -> SNat ('Succ n)
-- | Implicit construction of `SNat` from type-level `Nat`.
class INat (n :: Nat) where
snat :: SNat n
instance INat 'Zero where
snat = SZero
instance INat n => INat (Succ n) where
snat = SSucc snat
-- Four eye smilies!
infixr 5 :<:, :>:
-- | In absense of better name: @HList@.
data HList :: Nat -> [Either * (Nat -> *)] -> * where
HNil :: HList n '[]
(:<:) :: a -> HList n as -> HList n (Left a ': as)
(:>:) :: IsEntity e => [Ref n e] -> HList n as -> HList n (Right e ': as)
newtype Reference (e :: Nat -> *) = Reference { unReference :: EntityId }
deriving (Eq, Ord, Show, Read)
type family Ref (n :: Nat) (e :: Nat -> *) :: * where
Ref Zero e = Reference e
Ref (Succ n) e = e n
-- | Like @foldr (->) b as@, but on the type-level
type family HApply (n :: Nat) (as :: [Either * (Nat -> *)]) (b :: *) :: * where
HApply n '[] b = b
HApply n (Left a ': as) b = a -> HApply n as b
HApply n (Right a ': as) b = [Ref n a] -> HApply n as b
-- | I'm kind of surprised this works!
hApply :: HApply n spec b -> HList n spec -> b
hApply f HNil = f
hApply f (a :<: as) = hApply (f a) as
hApply f (a :>: as) = hApply (f a) as
-- Class definitions:
-- | `IsRecord` is isomorphic with some `HList`.
class IsRecord (e :: Nat -> *) where
type Spec e :: [Either * (Nat -> *)]
toHList :: e n -> HList n (Spec e)
fromHList :: HList n (Spec e) -> e n
-- | `IsEntity` is familiar class
class IsEntity (e :: Nat -> *) where
toEntity :: Entity -> Maybe (e Zero)
entityId :: e n -> EntityId
unwrapExpl :: SNat n -> Ref n e -> DBMonad (e n)
default unwrapExpl :: IsRecord e => SNat n -> Ref n e -> DBMonad (e n)
unwrapExpl SZero ref = unwrapReference ref
unwrapExpl (SSucc sn) ent = fromHList <$> unwrapHList sn (toHList ent)
wrapExpl :: SNat n -> e n -> Ref n e
default wrapExpl :: IsRecord e => SNat n -> e n -> Ref n e
wrapExpl SZero ent = Reference (entityId ent)
wrapExpl (SSucc sn) ent = fromHList . wrapHList sn . toHList $ ent
unwrap :: (IsEntity e, INat n) => Ref n e -> DBMonad (e n)
unwrap = unwrapExpl snat
wrap :: (IsEntity e, INat n) => e n -> Ref n e
wrap = wrapExpl snat
-- Unwrapping helpers
unwrapReference :: IsEntity e => Reference e -> DBMonad (e Zero)
unwrapReference (Reference eid) = do
entity <- askEntity eid
case toEntity entity of
Just pl -> return pl
Nothing -> throwError $ "can't parse entity -- " ++ show eid
unwrapHList :: SNat n -> HList n spec -> DBMonad (HList (Succ n) spec)
unwrapHList _ HNil = pure HNil
unwrapHList sn (x :<: xs) = (x :<:) <$> unwrapHList sn xs
unwrapHList sn (e :>: xs) = (:>:) <$> traverse (unwrapExpl sn) e <*> unwrapHList sn xs
wrapHList :: SNat n -> HList (Succ n) spec -> HList n spec
wrapHList _ HNil = HNil
wrapHList sn (x :<: xs) = x :<: wrapHList sn xs
wrapHList sn (e :>: xs) = map (wrapExpl sn) e :>: wrapHList sn xs
------------------------------------------------------------------------
-- User code:
--
data PL (n :: Nat) = PL
{ plId :: EntityId
, plName :: String
, plUrl :: String
, plAppearedIn :: Maybe Int
, plTypingDis :: [String]
, plInfluenced :: [Ref n PL]
}
deriving instance Eq (Ref n PL) => Eq (PL n)
-- | Structure of `PL`.
--
-- >>> :kind PLSpec
-- PLSpec :: [Either * (Nat -> *)]
type PLSpec = '[ Left EntityId, Left String, Left String, Left (Maybe Int), Left [String], Right PL ]
instance IsRecord PL where
type Spec PL = PLSpec
-- This is the only boilerplate code.
-- Unfortunately, it cannot be derivied using Generics, at least elegantly.
-- But should be not hard using Template Haskell
toHList PL {..} = plId :<: plName :<: plUrl :<: plAppearedIn :<: plTypingDis :<: plInfluenced :>: HNil
fromHList hlist = hApply PL hlist
instance IsEntity PL where
entityId = plId
toEntity ent = PL (DB.entityId ent) <$> getAttr "title" ent
<*> getAttr "url" ent
<*> pure (getAttr "appearedIn" ent)
<*> getAttr "typingDiscipline" ent
<*> (fmap Reference <$> getAttr "influenced" ent)
------------------------------------------------------------------------
-- Test code
--
-- $setup
-- >>> Right db <- readDB "pl.json"
cEntityId :: EntityId
cEntityId = EntityId 32
-- | Let's try ML
-- >>> fmap plName <$> runDBMonad clang db
-- Right (Just "C")
clang :: DBMonad (Maybe (PL Zero))
clang = toEntity <$> askEntity cEntityId
prettyPL' :: PL n -> String -> String
prettyPL' PL {..} influencedStr =
intercalate "\n" [ "name: " ++ plName
, "url: " ++ plUrl
, "appeared in: " ++ maybe "-" show plAppearedIn
, "typing: " ++ intercalate ", " plTypingDis
, "influenced: " ++ influencedStr
]
prettierPL :: PL (Succ n) -> String
prettierPL pl @ PL { plInfluenced = influenced } =
prettyPL' pl . intercalate ", " . sort . map plName $ influenced
cUnwrappedTwice :: DBMonad (PL (Succ (Succ Zero)))
cUnwrappedTwice = return (Reference cEntityId) >>= unwrap >>= unwrap >>= unwrap
-- | Let's try!
--
-- >>> let Right pl = runDBMonad cUnwrappedTwice db
-- >>> putStrLn $ prettierPL pl
-- name: C
-- url: http://en.wikipedia.org/wiki/C_(programming_language)
-- appeared in: 1972
-- typing: static, weak, manifest, nominal
-- influenced: AMPL, AWK, BitC, C Sharp, ...
-- | Error case!
--
-- >>> runDBMonad (prettierPL <$> return (Reference cEntityId) >>= unwrap) db
-- ...
-- Couldn't match expected type ...
-- ...
-- | And `wrap`
--
-- >>> let Right pl = runDBMonad (wrap <$> cUnwrappedTwice) db
-- >>> putStrLn $ prettierPL pl
-- name: C
-- url: http://en.wikipedia.org/wiki/C_(programming_language)
-- appeared in: 1972
-- typing: static, weak, manifest, nominal
-- influenced: AMPL, AWK, BitC, C Sharp, ...
-- | Equality: `(==)`
--
-- >>> let Right pl = runDBMonad cUnwrappedTwice db
-- >>> pl == pl
-- True