packages feed

hs-di 0.2.0 → 0.2.1

raw patch · 2 files changed

+173/−2 lines, 2 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

+ README.md view
@@ -0,0 +1,171 @@+# Haskell Dependency Injection++A promising Dependency Injection system for Haskell.++# Why++The main motivation behind this project is to make it very easy to mock dependencies of functions for unit testing, even if they are nested many levels deep.++Another motivation of mine was to find a technique that works entirely at compile time, having the following benefits:++- compile-time type checking of all dependencies and wether they fit together+- no run-time performance penalty+- no run-time Dependency Injection related errors++# Example++A motivating example:++```haskell+-- Lib.hs+{-# language TemplateHaskell #-}++module Lib where++import DI++inj+noun = "World"++inj+sentence noun = "Hello " ++ noun++inj+statement sentence = sentence ++ "!"+```++```haskell+-- Spec.hs+{-# language TemplateHaskell #-}++import DI+import Lib++inj+nounMock = "Dear Reader" ++main = do+  $(assemble statementD) `shouldBe` "Hello World!"+  $(assemble $ override "noun" "nounMock" $ statementD) `shouldBe` "Hello Dear Reader!"++-- assertion function+shouldBe = shouldBeF show+shouldBeF f actual expected | actual == expected = putStrLn $ "OK " ++ f actual+                            | otherwise          = error $ "FAIL " ++ f actual ++ " /= " ++ f expected+```++Which when executed should output:++```+OK "Hello World!"+OK "Hello Dear Reader!"+```++*Note: `noun` is being overwritten while we are testing `statement`, which is not an immediate dependency but rather at the depth of 2.*++# How++In this project I am trying to emulate the manual assembly+of deeply nested and injected dependencies with the help of TemplateHaskell+and config compile-time dependency graphs.++To go into more details, this is what happens behind the scenes in the above example:++```haskell+Lib.hs:8:1-3: Splicing declarations+    inj+  ======>+    nounD = Dep "noun" []+    nounT = (noun)+    nounA = noun+    nounI = noun+Lib.hs:11:1-3: Splicing declarations+    inj+  ======>+    sentenceD = Dep "sentence" [nounD]+    sentenceT = (sentence, nounT)+    sentenceA = sentence nounA+    sentenceI = sentence+Lib.hs:14:1-3: Splicing declarations+    inj+  ======>+    statementD = Dep "statement" [sentenceD]+    statementT = (statement, sentenceT)+    statementA = statement sentenceA+    statementI = statement+```+```haskell+Spec.hs:7:1-3: Splicing declarations+    inj+  ======>+    nounMockD = Dep "nounMock" []+    nounMockT = (nounMock)+    nounMockA = nounMock+    nounMockI = nounMock+Spec.hs:11:5-23: Splicing expression+    assemble statementD+  ======>+    let (statement, (sentence, noun)) = statementT+    in statement (sentence noun)+Spec.hs:12:5-54: Splicing expression+    assemble $ override "noun" "nounMock" $ statementD+  ======>+    let (statement, (sentence, _)) = statementT+    in statement (sentence nounMock)+```++A couple things to note:++- You may be wondering what the suffix letters mean in the declarations.+  +  You don't have to concern yourself with them, it's part of the internal hidden API of the DI framework by design.+  +  (If you are curious however, they stand for "Dependency definitions/`Defs`", "Tuple", "Assembled", and "Injectable", respectively.)+- As you can see, at the end of the day, all this machinery achieves pretty much the same what a developer would do by hand: `statement (sentence noun)`+  +  The beauty, however, is that this doesn't have to be done by hand, as it would become immensly tideous and time-consuming as soon as we start to handle more than a couple dependencies.+- Mocking is equally elegant:+  +  `let (statement, (sentence, _)) = statementT in statement (sentence nounMock)` +  +  (translated from `$(assemble $ override "noun" "nounMock" $ statementD)`)+++## To try++To execute and experiment with modifying the above example:++```shell+git clone git@github.com:Wizek/hs-di.git+cd hs-di/examples/simple+stack test+```++### Pros and cons of this approach++  - `(+)` Supports values to be injected+  - `(+)` Supports functions to be injected+  - `(++)` Supports overriding of arbitrary number and depth of dependencies+  - `(++)` Compile time type checking (despites strings being used, those too are checked)+  - `(+)` Supports type variables+  - `(+)` Theoretically also supports surgically only overriding some subsets of dependencies++  - `(+)` Emulates how a human would do DI by hand, and does the hard work automatically++  - `(+)` Some module support++    - `(-0.5)` The module support is not yet fully perfect++  - `(?)` How is performance impacted? Does GHC notice `f (g x) (g x)`?++### Todos++- [x] TODO: make multiple argumetns work+- [x] TODO: Simplify Deps+- [x] TODO: reorder arguments of override+- [x] TODO: try with some real-life code+- [x] TODO: Write quasi quoter or TH splicer that writes the `Deps` definitions too+- [x] TODO: look for a way to have full module support (without having to explicitly re-export and risk name-clashes)+- [ ] TODO: have GHC support Dec TH splices in let bindings: https://ghc.haskell.org/trac/ghc/ticket/9880#comment:7+      Which could make overriding dependencies with mocks more pleasant+- [ ] TODO: have GHC lift stage restriction
hs-di.cabal view
@@ -1,5 +1,5 @@ name:                hs-di-version:             0.2.0+version:             0.2.1 synopsis:            Dependency Injection library for Haskell description:         Dependency Injection library for Haskell to allow powerful unit testing and mocking (compile-time type-checked) homepage:            https://github.com/Wizek/hs-di#readme@@ -10,7 +10,7 @@ copyright:           2016 Milan Nagy category:            Web build-type:          Simple--- extra-source-files:+extra-source-files:  README.md cabal-version:       >=1.10  library