packages feed

polysemy-methodology 0.1.2.0 → 0.1.2.1

raw patch · 2 files changed

+246/−1 lines, 2 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

README.md view
@@ -8,6 +8,7 @@ prog :: Members '[ Input a                  , Methodology a b                  , Output b]+     => Sem r () prog = input @a >>= process @a @b >>= output @b ``` @@ -25,3 +26,247 @@ This allows you to work up a solution to a domain problem backwards, by running the program you intend to solve directly and using holes to guide the requirements.++## Worked example++A worked example of this approach can be found in the+[flashblast](https://gitlab.com/homotopic-tech/flashblast)+repository. In this we want to take a configuration in dhall,+and process it in some way an output of flashcards.++We might model this as such:++```+-- Domain.hs+import Polysemy+import Polysemy.Input+import Polysemy.Tagged+import Polysemy.Methodology+import Polysemy.Output++-- | A `DeckConfiguration` indicates how we create cards.+data DeckConfiguration++-- | A `CollectionsPackage` indicates.+data CollectionsPackage++-- | The Construction Methodology for flashblast.+data ConstructionMethodology++-- | `flashblast` is a program that takes a `DeckConfiguration` and outputs a `CollectionsPackage`.+flashblast :: Members '[ Tagged DeckConfiguration (Input a)+                       , Tagged ConstructionMethodology (Methodology a b)+                       , Tagged CollectionsPackage (Output b)] r+           => Sem r ()+flashblast = do+  x <- tag @DeckConfiguration input+  k <- tag @ConstructionMethodology $ process x+  tag @CollectionsPackage $ output k+```++Notice that this is an abstract domain model. We have not committed to+a particular representation of any of the three elements of this program.+In fact, this file depends only on polysemy modules, which allows+us to isolate the domain model from anything resembling real code.++However, we would also like to claim that what we say the program *should*+do in abstraction is *actually* what we run for real. So it would be+reassuring to be able to simply interpret this into real functions.++We commit to a concrete representation for the config and for+the output only in the main application file, where we iterate over+the decks.++```+-- Config.hs+data Spec =+    Pronunciation   [PronunciationSpec]+  | Excerpt         [ExcerptSpec]+  | BasicReversed   [BasicReversedCard]+  | MinimalReversed [MinimalReversedCard]+    deriving stock Generic+    deriving D.FromDhall+      via D.Codec (D.Field (D.DropPrefix "_")) Spec++makePrisms ''Spec++data Part = Part {+  _outfile :: Path Rel File+, _spec    :: Spec+} deriving Generic+  deriving D.FromDhall+    via D.Codec (D.Field (D.DropPrefix "_")) Part++data Deck = Deck {+  _resourceDirs :: ResourceDirs+, _exportDirs   :: ExportDirs+, _parts        :: [Part]+} deriving stock Generic+  deriving D.FromDhall+    via D.Codec (D.Field (D.DropPrefix "_")) Deck+```++```+-- Main.hs+data Deck = Deck {+  notes :: Map (Path Rel File) Text+, media :: [Path Rel File]+} deriving stock (Eq, Show, Generic)+  deriving Semigroup via GenericSemigroup Deck+  deriving Monoid via GenericMonoid Deck++main = do+  Config.FlashBlast{..} <- D.input D.auto "./index.dhall"+  forM_ _decks $ \x -> do+    flashblast @Config.Deck @Deck+      & runM+```++Here we will be told that we need to satisfy the `Input`, `Output` and+`Methodology` effects.++The `Config.Deck` is divided into several different specs. We could simply+write one giant function to solve the `Methodology` and annihilate the +`Methodology` effect using `runMethodologySem`.++```+soln :: Members '[...] r => Config.Deck -> Sem r Deck+soln = ...++-- runMethodologySem @Config.Deck @Deck soln+```++But this would conflate our concerns - the different specs require different+effects to execute, and having this single function require all effects+wouuld be maintenance should we choose to remove any functionality. It+would also increase our testing surface.++* The `MinimalReversedCard`s and `BasicReversedCard`s are direct+  representations of what the output cards should look like, and so can be+purely transformed.+* `ExcerptSpec`s need to be transformed into cards by way of processing+  the specified video and subtitle track via ffmpeg.+* `PronunciationSpec` need to fetch the pronunciation data for the target+  words from a remote API.++What would be nice is if we could reach a point where we can make functions+for each of with their respective effects isolated but without having to+agglomerate all the effects into a single solution function.++It makes sense then to take our `Methodology` and break it down into sub+`Methodology`s that can be reasoned about independently, rather than trying+to satisfy the program with one function built up from parts. This way+we can break the program down using only type applications and interpreters,+and we only need to write any code once we are happy that the problem is+sufficiently decomposed.++The interpreters in this library aree operations that consume a `Methodology`+and turn it into parts.++`cutMethodology` breaks the `Methodology` into two pieces, and will then+require interpreters for each. So if we start with a `Methodology b d`, we+can break it into `Methodology b c` and `Methodology c d`, each of which+will require some solution. This is essentially reverse arrow composition.++```+b -----> d   ===>  (b ---> c), (c ---> d)+```++`divideMethodology` breaks the target into a pair, and connects+the source to both of them, producing three `Methodology`s we need to solve. This is reverse fanout.++```+b ----> d ==> (b ---> c), (b ---> c'), ((c,c') ----> d)+```++`decideMethodology` breaks the source into an `Either`, allowing us to+choose a `Methodology` to run as the result of another `Methodology`+based on the source. This is reverse fanin.++```+b ----> d ===> (b---> Either c c'), (c ---> d), (c ---> d)+```++`decomposeMethodology` is `cutMethodology` specialised to+`HList` as the center argument. This allows us to cut the+`Methodology` into multiple parallel tracks.++```+                /-----c-----\+b ----> d ===> b------d------f+                \-----e-----/+```++Back to our example, we need to decompose our `Config` into+the problems concerning each type of spec, then turn+each of those into a `Deck` of its own, then collect the+produced decks monoidally into the final output.++Dealing with HLists is a little awkward but the approach that+will work is to deal with each strand individually, and use+`separateMethodologyInitial` or `separateMethodologyTerminal`+depending on whether the strand appears before or after the+`HList`, which will separate the element of the `HList` into+its own `Methodology`. Then, decompose this further or solve+it.++```+type DeckSplit = '[Map (Path Rel File) [Config.MinimalReversedCard]+                 , Map (Path Rel File) [Config.BasicReversedCard]+                 , Map (Path Rel File) [Config.ExcerptSpec]+                 , Map (Path Rel File) [Config.PronunciationSpec]+                 ]++type FileMap b = Map (Path b File)++extractParts :: Prism' Config.Spec x -> Config.Deck -> Map (Path Rel File) x+extractParts x = Map.fromList . itoListOf+                  ( Config.parts+                  % itraversed+                  %> reindexed (view Config.outfile) selfIndex+                  % Config.spec+                  % x+                  )++++main = do+  Config.FlashBlast{..} <- D.input D.auto "./index.dhall"+  forM_ _decks $ \x -> do+    flashblast @Config.Deck @Deck+      & untag @ConstructionMethodology+      & decomposeMethodology @Config.Deck @DeckSplit @Deck+        -- We pull out `Config.Deck -> (FileMap Rel [Config.MinimalReversedCard]` as its own `Methodology`.+        & separateMethodologyInitial @Config.Deck @(FileMap Rel [Config.MinimalReversedCard])+          -- And then immediately solve it purely.+          & runMethodologyPure (extractParts Config._MinimalReversed)+        -- and do the same for each one in the list.+        & separateMethodologyInitial @Config.Deck @(FileMap Rel [Config.BasicReversedCard])+          & runMethodologyPure (extractParts Config._BasicReversed)+        & separateMethodologyInitial @Config.Deck @(FileMap Rel [Config.ExcerptSpec])+          & runMethodologyPure (extractParts Config._Excerpt)+        & separateMethodologyInitial @Config.Deck @(FileMap Rel [Config.PronunciationSpec])+          & runMethodologyPure (extractParts Config._Pronunciation)+        & endMethodologyInitial+          & separateMethodologyTerminal @(FileMap Rel [Config.MinimalReversedCard]) @Deck+            & runMethodologyPure _+          & separateMethodologyTerminal @(FileMap Rel [Config.BasicReversedCard]) @Deck+            & runMethodologyPure _+          & separateMethodologyTerminal @(FileMap Rel [Config.ExcerptSpec]) @Deck+            & runMethodologySem _+          & separateMethodologyTerminal @(FileMap Rel [Config.PronunciationSpec]) @Deck+            & runMethodologySem _+```++We have left holes that polysemy will now tell us need to be+filled by nice clean `a -> b` or `a -> Sem r b` functions.+Any effects we add here we can deal with after this block, or+we can decompose this even further (see flashblast for details).++## Notes++There are intended to be less boilerplatey ways to deal with+separation, as a very common pattern is simply to separate a+strand out and then immediately solve it, but this library is+early and I didn't want to jump the gun with too many+functions.
polysemy-methodology.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           polysemy-methodology-version:        0.1.2.0+version:        0.1.2.1 synopsis:       Domain modelling algebra for polysemy category:       Polysemy author:         Daniel Firth