diff options
| -rw-r--r-- | provider/posts/events/01.lhs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/provider/posts/events/01.lhs b/provider/posts/events/01.lhs new file mode 100644 index 0000000..fd1bd03 --- /dev/null +++ b/provider/posts/events/01.lhs | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | --- | ||
| 2 | title: "On the Design of a Turing-Complete Appointment Book" | ||
| 3 | published: 2016-05-27 | ||
| 4 | tags: events | ||
| 5 | repo: https://git.yggdrasil.li/gkleen/pub/events | ||
| 6 | base: https://git.yggdrasil.li/gkleen/pub/events/tree/events/src/Events/Types.hs?id=e04e707b1fb63b7857878e2d77c560abe3efd51b | ||
| 7 | --- | ||
| 8 | |||
| 9 | I have a long history of using digital appointment books (calendars) | ||
| 10 | and not being satisfied with them (I´m currently being frustrated by | ||
| 11 | [Google Calendar][] after a long history of using [Remind][]). | ||
| 12 | Thus, of course, I had to implement my own, because, as always, all | ||
| 13 | existing software does not fullfill my exceedingly unrealistic | ||
| 14 | expectations with respect to customizability and extendability. | ||
| 15 | |||
| 16 | For now all I want from my appointment book is the ability to, given | ||
| 17 | an interval of time, print a list of events in some machine-parsable | ||
| 18 | format (probably [JSON][]). | ||
| 19 | |||
| 20 | > {-# LANGUAGE TemplateHaskell #-} | ||
| 21 | > {-# LANGUAGE MultiParamTypeClasses #-} | ||
| 22 | > {-# LANGUAGE FlexibleInstances #-} | ||
| 23 | > {-# LANGUAGE UndecidableInstances #-} | ||
| 24 | > | ||
| 25 | > module Events.Types | ||
| 26 | > ( TimeRange(..), rangeStart, rangeDuration | ||
| 27 | > , Event(..), payload, occursWithin | ||
| 28 | > , EvalCtx(..), ctxVars, ctxEvents | ||
| 29 | > , ObjCtx(..), objOccurs, objPayload | ||
| 30 | > , Eval, evaluate | ||
| 31 | > , module Data.Aeson | ||
| 32 | > , module Data.Time.Clock | ||
| 33 | > , module Data.Default.Class | ||
| 34 | > ) where | ||
| 35 | > | ||
| 36 | > import Control.Lens.TH | ||
| 37 | > | ||
| 38 | > import Data.Aeson (Object) | ||
| 39 | > | ||
| 40 | > import Data.Time.Clock | ||
| 41 | > | ||
| 42 | > import Control.Monad.State.Lazy | ||
| 43 | > import ListT (ListT) | ||
| 44 | > import qualified ListT | ||
| 45 | > | ||
| 46 | > import Data.Default.Class | ||
| 47 | > | ||
| 48 | > import Data.Monoid | ||
| 49 | > import Control.Monad.Fix | ||
| 50 | > import Control.Lens | ||
| 51 | > import Data.Maybe | ||
| 52 | |||
| 53 | We can quite easily encode an interval of time as a lower bound and a | ||
| 54 | duration: | ||
| 55 | |||
| 56 | > data TimeRange = TimeRange | ||
| 57 | > { _rangeStart :: UTCTime | ||
| 58 | > , _rangeDuration :: NominalDiffTime | ||
| 59 | > } | ||
| 60 | > makeLenses ''TimeRange | ||
| 61 | |||
| 62 | For our purposes it´s sufficient to consider an event to be some data | ||
| 63 | we´ll display when needed and some way to determine whether the given | ||
| 64 | `TimeRange`{.haskell} intersects it: | ||
| 65 | |||
| 66 | > data Event = Event | ||
| 67 | > { _payload :: Object | ||
| 68 | > , _occursWithin :: TimeRange -> Bool | ||
| 69 | > } | ||
| 70 | > makeLenses ''Event | ||
| 71 | |||
| 72 | We are going to want to parse a specification of some kind into a form we can run. | ||
| 73 | Below we see one such form. | ||
| 74 | |||
| 75 | `ListT`{.haskell} allows for nondeterministic computation – it allows us to split | ||
| 76 | our wordline and continue depth-first much like `[]`{.haskell}. | ||
| 77 | |||
| 78 | Within every wordline we modify a distinct snapshot of `ObjCtx`{.haskell} we took | ||
| 79 | while branching. | ||
| 80 | |||
| 81 | We also share one `EvalCtx`{.haskell} across all worldlines. | ||
| 82 | |||
| 83 | > type Eval m a = StateT ObjCtx (ListT (StateT EvalCtx m)) a | ||
| 84 | |||
| 85 | `ListT`{.haskell} does not ship with extensive support for the | ||
| 86 | [transformers package][transformers]: | ||
| 87 | |||
| 88 | > instance MonadState s m => MonadState s (ListT m) where | ||
| 89 | > get = lift get | ||
| 90 | > put = lift . put | ||
| 91 | |||
| 92 | The context shared among all worldlines mainly contains all objects that | ||
| 93 | eventually get computed – haskells lazyness ensures that we terminate as | ||
| 94 | long as we don´t have objects depend on themselves in a sufficiently | ||
| 95 | degenerate way. | ||
| 96 | |||
| 97 | > data EvalCtx = EvalCtx | ||
| 98 | > { _ctxEvents :: [Object] | ||
| 99 | > } deriving (Show) | ||
| 100 | > makeLenses ''EvalCtx | ||
| 101 | > | ||
| 102 | > instance Default EvalCtx where | ||
| 103 | > def = EvalCtx | ||
| 104 | > { _ctxEvents = mempty | ||
| 105 | > } | ||
| 106 | |||
| 107 | Every wordline constructs exactly one object while having access to a set | ||
| 108 | of declarations that can occur anywhere on the wordline. | ||
| 109 | |||
| 110 | > data ObjCtx = ObjCtx | ||
| 111 | > { _objOccurs :: Bool | ||
| 112 | > , _objPayload :: Maybe Object | ||
| 113 | > , _objVars :: Object | ||
| 114 | > } | ||
| 115 | > makeLenses ''ObjCtx | ||
| 116 | > | ||
| 117 | > instance Default ObjCtx where | ||
| 118 | > def = ObjCtx | ||
| 119 | > { _objOccurs = False | ||
| 120 | > , _objPayload = Nothing | ||
| 121 | > , _objVars = mempty | ||
| 122 | > } | ||
| 123 | |||
| 124 | Constructing an `Object`{.haskell} from an `ObjCtx`{.haskell} is straightforward: | ||
| 125 | |||
| 126 | > objCtx :: ObjCtx -> Maybe Object | ||
| 127 | > objCtx (ObjCtx False _) = Nothing | ||
| 128 | > objCtx (ObjCtx True o) = o | ||
| 129 | |||
| 130 | `mfix`{.haskell} allows all values contained within our various `StateT`{.haskell} | ||
| 131 | layers to depend on one another: | ||
| 132 | |||
| 133 | > evaluate :: MonadFix m => Eval m () -> m [Object] | ||
| 134 | > evaluate x = catMaybes <$> mfix x' | ||
| 135 | > where | ||
| 136 | > x' = evalStateT (ListT.toList (objCtx <$> execStateT x def)) . flip (set ctxEvents) def . catMaybes | ||
| 137 | |||
| 138 | [Google Calendar]: <https://calendar.google.com> | ||
| 139 | [Remind]: <https://www.roaringpenguin.com/products/remind> | ||
| 140 | [JSON]: <https://en.wikipedia.org/wiki/JSON> | ||
| 141 | [monad transformers]: <https://en.wikibooks.org/wiki/Haskell/Monad_transformers> | ||
| 142 | [transformers]: <https://hackage.haskell.org/package/transformers> | ||
