From: Evgenii Akentev Date: Tue, 5 Jan 2021 11:47:31 +0000 (+0500) Subject: Add simple, records and backpack versions. X-Git-Url: https://git.ak3n.com/?a=commitdiff_plain;h=f582dbf4c699aba02b96d5c58179b61b63096f29;p=handle-examples.git Add simple, records and backpack versions. --- diff --git a/README.md b/README.md index 7632ba3..8b12c7c 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# backpack-handle-example \ No newline at end of file +# backpack-handle-example + +The examples of the Handle pattern in simple case, with records, and backpack. diff --git a/backpack-handle/CHANGELOG.md b/backpack-handle/CHANGELOG.md new file mode 100644 index 0000000..806c22c --- /dev/null +++ b/backpack-handle/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for backpack-handle-pattern + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/backpack-handle/LICENSE b/backpack-handle/LICENSE new file mode 100644 index 0000000..9eea539 --- /dev/null +++ b/backpack-handle/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Evgenii Akentev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backpack-handle/Main.hs b/backpack-handle/Main.hs new file mode 100644 index 0000000..07cd3de --- /dev/null +++ b/backpack-handle/Main.hs @@ -0,0 +1,13 @@ +module Main where + +import qualified SuperWeatherProvider +import qualified WeatherProvider +import qualified WeatherReporter + +-- | This is an actual application where we use +-- our concrete implementation of `WeatherProvider`. +main :: IO () +main = do + let wph = SuperWeatherProvider.new + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon wph + putStrLn weatherReportInLondon diff --git a/backpack-handle/Setup.hs b/backpack-handle/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/backpack-handle/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/backpack-handle/backpack-handle.cabal b/backpack-handle/backpack-handle.cabal new file mode 100644 index 0000000..c427729 --- /dev/null +++ b/backpack-handle/backpack-handle.cabal @@ -0,0 +1,47 @@ +cabal-version: >=2 +name: backpack-handle +version: 0.1.0.0 +license-file: LICENSE +author: Evgenii Akentev +maintainer: i@ak3n.com +build-type: Simple +extra-source-files: CHANGELOG.md + +library domain + hs-source-dirs: domain + signatures: WeatherProvider + exposed-modules: WeatherReporter + default-language: Haskell2010 + build-depends: base + +library impl + hs-source-dirs: impl + exposed-modules: SuperWeatherProvider + reexported-modules: SuperWeatherProvider as WeatherProvider + default-language: Haskell2010 + build-depends: base + +library test-impl + hs-source-dirs: test-impl + exposed-modules: TestWeatherProvider + reexported-modules: TestWeatherProvider as WeatherProvider + default-language: Haskell2010 + build-depends: base + +executable backpack-handle-exe + main-is: Main.hs + build-depends: base >=4.13 && <4.14 + , impl + , domain + default-language: Haskell2010 + +test-suite spec + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Test.hs + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 + , QuickCheck + , hspec + , domain + , test-impl diff --git a/backpack-handle/domain/WeatherProvider.hsig b/backpack-handle/domain/WeatherProvider.hsig new file mode 100644 index 0000000..56de95a --- /dev/null +++ b/backpack-handle/domain/WeatherProvider.hsig @@ -0,0 +1,14 @@ +signature WeatherProvider where + +data Temperature +instance Show Temperature + +data WeatherData = WeatherData { temperature :: Temperature } + +type Location = String +type Day = String + +data Handle + +-- | The interface of `WeatherProvider` with available methods. +getWeatherData :: Handle -> Location -> Day -> IO WeatherData diff --git a/backpack-handle/domain/WeatherReporter.hs b/backpack-handle/domain/WeatherReporter.hs new file mode 100644 index 0000000..e096750 --- /dev/null +++ b/backpack-handle/domain/WeatherReporter.hs @@ -0,0 +1,11 @@ +module WeatherReporter where + +import WeatherProvider + +type WeatherReport = String + +-- | This is domain logic. It uses `WeatherProvider` to get the actual data. +getCurrentWeatherReportInLondon :: WeatherProvider.Handle -> IO WeatherReport +getCurrentWeatherReportInLondon wph = do + weatherData <- WeatherProvider.getWeatherData wph "London" "now" + return $ "Current temperature in London is " ++ (show $ temperature weatherData) \ No newline at end of file diff --git a/backpack-handle/impl/SuperWeatherProvider.hs b/backpack-handle/impl/SuperWeatherProvider.hs new file mode 100644 index 0000000..1a29069 --- /dev/null +++ b/backpack-handle/impl/SuperWeatherProvider.hs @@ -0,0 +1,16 @@ +module SuperWeatherProvider where + +type Temperature = Int +data WeatherData = WeatherData { temperature :: Temperature } + +type Location = String +type Day = String + +data Handle = Handle + +new :: Handle +new = Handle + +-- | This is some concrete implementation `WeatherProvider` interface +getWeatherData :: Handle -> Location -> Day -> IO WeatherData +getWeatherData _ _ _ = return $ WeatherData 30 diff --git a/backpack-handle/test-impl/TestWeatherProvider.hs b/backpack-handle/test-impl/TestWeatherProvider.hs new file mode 100644 index 0000000..900b102 --- /dev/null +++ b/backpack-handle/test-impl/TestWeatherProvider.hs @@ -0,0 +1,23 @@ +module TestWeatherProvider where + +type Temperature = Int +data WeatherData = WeatherData { temperature :: Temperature } + +type Location = String +type Day = String + +-- | This is a configuration that allows to setup the provider for tests. +data Config = Config + { initTemperature :: Temperature + } + +data Handle = Handle + { config :: Config + } + +new :: Config -> Handle +new = Handle + +-- | This is an implementation `WeatherProvider` interface for tests +getWeatherData :: Handle -> Location -> Day -> IO WeatherData +getWeatherData (Handle conf) _ _ = return $ WeatherData $ initTemperature conf diff --git a/backpack-handle/test/Test.hs b/backpack-handle/test/Test.hs new file mode 100644 index 0000000..18dd2e5 --- /dev/null +++ b/backpack-handle/test/Test.hs @@ -0,0 +1,22 @@ +import Test.Hspec + +import qualified TestWeatherProvider +import qualified WeatherProvider +import qualified WeatherReporter + +main :: IO () +main = hspec spec + +weatherWithTemp :: WeatherProvider.Temperature -> WeatherProvider.Handle +weatherWithTemp = TestWeatherProvider.new . TestWeatherProvider.Config + +spec :: Spec +spec = describe "WeatherReporter" $ do + it "weather in London is 0" $ do + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon $ + weatherWithTemp 0 + weatherReportInLondon `shouldBe` "Current temperature in London is 0" + it "weather in London is -5" $ do + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon $ + weatherWithTemp (-5) + weatherReportInLondon `shouldBe` "Current temperature in London is -5" diff --git a/records-handle/CHANGELOG.md b/records-handle/CHANGELOG.md new file mode 100644 index 0000000..806c22c --- /dev/null +++ b/records-handle/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for backpack-handle-pattern + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/records-handle/LICENSE b/records-handle/LICENSE new file mode 100644 index 0000000..9eea539 --- /dev/null +++ b/records-handle/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Evgenii Akentev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/records-handle/Main.hs b/records-handle/Main.hs new file mode 100644 index 0000000..07cd3de --- /dev/null +++ b/records-handle/Main.hs @@ -0,0 +1,13 @@ +module Main where + +import qualified SuperWeatherProvider +import qualified WeatherProvider +import qualified WeatherReporter + +-- | This is an actual application where we use +-- our concrete implementation of `WeatherProvider`. +main :: IO () +main = do + let wph = SuperWeatherProvider.new + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon wph + putStrLn weatherReportInLondon diff --git a/records-handle/Setup.hs b/records-handle/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/records-handle/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/records-handle/domain/WeatherProvider.hs b/records-handle/domain/WeatherProvider.hs new file mode 100644 index 0000000..9375c4d --- /dev/null +++ b/records-handle/domain/WeatherProvider.hs @@ -0,0 +1,12 @@ +module WeatherProvider where + +type Temperature = Int +data WeatherData = WeatherData { temperature :: Temperature } + +type Location = String +type Day = String + +-- | The interface of `WeatherProvider` with available methods. +data Handle = Handle + { getWeatherData :: Location -> Day -> IO WeatherData + } diff --git a/records-handle/domain/WeatherReporter.hs b/records-handle/domain/WeatherReporter.hs new file mode 100644 index 0000000..e096750 --- /dev/null +++ b/records-handle/domain/WeatherReporter.hs @@ -0,0 +1,11 @@ +module WeatherReporter where + +import WeatherProvider + +type WeatherReport = String + +-- | This is domain logic. It uses `WeatherProvider` to get the actual data. +getCurrentWeatherReportInLondon :: WeatherProvider.Handle -> IO WeatherReport +getCurrentWeatherReportInLondon wph = do + weatherData <- WeatherProvider.getWeatherData wph "London" "now" + return $ "Current temperature in London is " ++ (show $ temperature weatherData) \ No newline at end of file diff --git a/records-handle/impl/SuperWeatherProvider.hs b/records-handle/impl/SuperWeatherProvider.hs new file mode 100644 index 0000000..9eef94a --- /dev/null +++ b/records-handle/impl/SuperWeatherProvider.hs @@ -0,0 +1,12 @@ +module SuperWeatherProvider where + +import WeatherProvider + +new :: Handle +new = Handle + { getWeatherData = getSuperWeatherData + } + +-- | This is some concrete implementation `WeatherProvider` interface +getSuperWeatherData :: Location -> Day -> IO WeatherData +getSuperWeatherData _ _ = return $ WeatherData 30 \ No newline at end of file diff --git a/records-handle/records-handle.cabal b/records-handle/records-handle.cabal new file mode 100644 index 0000000..ac1318e --- /dev/null +++ b/records-handle/records-handle.cabal @@ -0,0 +1,47 @@ +cabal-version: >=2 +name: records-handle +version: 0.1.0.0 +license-file: LICENSE +author: Evgenii Akentev +maintainer: i@ak3n.com +build-type: Simple +extra-source-files: CHANGELOG.md + +library domain + hs-source-dirs: domain + exposed-modules: WeatherProvider + , WeatherReporter + default-language: Haskell2010 + build-depends: base + +library impl + hs-source-dirs: impl + exposed-modules: SuperWeatherProvider + default-language: Haskell2010 + build-depends: base + , domain + +library test-impl + hs-source-dirs: test-impl + exposed-modules: TestWeatherProvider + default-language: Haskell2010 + build-depends: base + , domain + +executable records-handle-exe + main-is: Main.hs + build-depends: base >=4.13 && <4.14 + , domain + , impl + default-language: Haskell2010 + +test-suite spec + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Test.hs + default-language: Haskell2010 + build-depends: base >= 4.7 && < 5 + , QuickCheck + , hspec + , domain + , test-impl diff --git a/records-handle/test-impl/TestWeatherProvider.hs b/records-handle/test-impl/TestWeatherProvider.hs new file mode 100644 index 0000000..172dea4 --- /dev/null +++ b/records-handle/test-impl/TestWeatherProvider.hs @@ -0,0 +1,17 @@ +module TestWeatherProvider where + +import WeatherProvider + +-- | This is a configuration that allows to setup the provider for tests. +data Config = Config + { initTemperature :: Temperature + } + +new :: Config -> Handle +new config = Handle + { getWeatherData = getTestWeatherData $ initTemperature config + } + +-- | This is an implementation `WeatherProvider` interface for tests +getTestWeatherData :: Int -> Location -> Day -> IO WeatherData +getTestWeatherData temp _ _ = return $ WeatherData temp diff --git a/records-handle/test/Test.hs b/records-handle/test/Test.hs new file mode 100644 index 0000000..18dd2e5 --- /dev/null +++ b/records-handle/test/Test.hs @@ -0,0 +1,22 @@ +import Test.Hspec + +import qualified TestWeatherProvider +import qualified WeatherProvider +import qualified WeatherReporter + +main :: IO () +main = hspec spec + +weatherWithTemp :: WeatherProvider.Temperature -> WeatherProvider.Handle +weatherWithTemp = TestWeatherProvider.new . TestWeatherProvider.Config + +spec :: Spec +spec = describe "WeatherReporter" $ do + it "weather in London is 0" $ do + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon $ + weatherWithTemp 0 + weatherReportInLondon `shouldBe` "Current temperature in London is 0" + it "weather in London is -5" $ do + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon $ + weatherWithTemp (-5) + weatherReportInLondon `shouldBe` "Current temperature in London is -5" diff --git a/simple-handle/CHANGELOG.md b/simple-handle/CHANGELOG.md new file mode 100644 index 0000000..806c22c --- /dev/null +++ b/simple-handle/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for backpack-handle-pattern + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/simple-handle/LICENSE b/simple-handle/LICENSE new file mode 100644 index 0000000..9eea539 --- /dev/null +++ b/simple-handle/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Evgenii Akentev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/simple-handle/Main.hs b/simple-handle/Main.hs new file mode 100644 index 0000000..e1d6702 --- /dev/null +++ b/simple-handle/Main.hs @@ -0,0 +1,10 @@ +module Main where + +import qualified WeatherProvider +import qualified WeatherReporter + +main :: IO () +main = do + let wph = WeatherProvider.new + weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon wph + putStrLn weatherReportInLondon diff --git a/simple-handle/Setup.hs b/simple-handle/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/simple-handle/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/simple-handle/domain/WeatherProvider.hs b/simple-handle/domain/WeatherProvider.hs new file mode 100644 index 0000000..d61ef7c --- /dev/null +++ b/simple-handle/domain/WeatherProvider.hs @@ -0,0 +1,19 @@ +module WeatherProvider where + +type Temperature = Int +data WeatherData = WeatherData { temperature :: Temperature } + +type Location = String +type Day = String + +-- | Our Handle is empty, but usually other dependencies are stored here +data Handle = Handle + +-- | Constructor for Handle +new :: Handle +new = Handle + +-- | This is some concrete implementation. +-- In this example we return a constant value. +getWeatherData :: Handle -> Location -> Day -> IO WeatherData +getWeatherData _ _ _ = return $ WeatherData 30 diff --git a/simple-handle/domain/WeatherReporter.hs b/simple-handle/domain/WeatherReporter.hs new file mode 100644 index 0000000..88a9d44 --- /dev/null +++ b/simple-handle/domain/WeatherReporter.hs @@ -0,0 +1,16 @@ +module WeatherReporter where + +import WeatherProvider + +type WeatherReport = String + +-- | Domain logic. Usually some pure code that might use mtl, free monads, etc. +createWeatherReport :: WeatherData -> WeatherReport +createWeatherReport (WeatherData temp) = + "The current temperature in London is " ++ (show temp) + +-- | Domain logic that uses external dependency to get data and process it. +getCurrentWeatherReportInLondon :: WeatherProvider.Handle -> IO WeatherReport +getCurrentWeatherReportInLondon wph = do + weatherData <- WeatherProvider.getWeatherData wph "London" "now" + return $ createWeatherReport weatherData diff --git a/simple-handle/simple-handle.cabal b/simple-handle/simple-handle.cabal new file mode 100644 index 0000000..2a349a8 --- /dev/null +++ b/simple-handle/simple-handle.cabal @@ -0,0 +1,21 @@ +cabal-version: >=2 +name: simple-handle +version: 0.1.0.0 +license-file: LICENSE +author: Evgenii Akentev +maintainer: i@ak3n.com +build-type: Simple +extra-source-files: CHANGELOG.md + +library domain + hs-source-dirs: domain + exposed-modules: WeatherProvider + , WeatherReporter + default-language: Haskell2010 + build-depends: base + +executable simple-handle-exe + main-is: Main.hs + build-depends: base >=4.13 && <4.14 + , domain + default-language: Haskell2010