Improve vinyl-handle example with multiple providers.
authorEvgenii Akentev <i@ak3n.com>
Sat, 9 Jan 2021 11:31:33 +0000 (16:31 +0500)
committerEvgenii Akentev <i@ak3n.com>
Sat, 9 Jan 2021 11:31:33 +0000 (16:31 +0500)
README.md
vinyl-handle/domain/QueryTypes.hs [new file with mode: 0644]
vinyl-handle/domain/TemperatureProvider.hs [new file with mode: 0644]
vinyl-handle/domain/WeatherProvider.hs
vinyl-handle/domain/WeatherReporter.hs
vinyl-handle/domain/WindProvider.hs [new file with mode: 0644]
vinyl-handle/impl/SuperWeatherProvider.hs
vinyl-handle/test-impl/TestWeatherProvider.hs
vinyl-handle/test/Test.hs
vinyl-handle/vinyl-handle.cabal

index 396d46e6e0dcd0eb1d73913637447611d5dbc470..053a18d3fec569934539c92f183c2d57bc405d77 100644 (file)
--- a/README.md
+++ b/README.md
@@ -9,3 +9,5 @@ This repository contains examples of [the Handle pattern](https://jaspervdj.be/p
 - `backpack-handle` does the same thing as `records-handle` but using Backpack instead. It allows us to specialize function calls.
 
 - `backpack-handles` goes further and makes `WeatherProvider` and `WeatherReporter` signatures.
+
+- `vinyl-handle` explores the design space using `vinyl` instead of records as Handle. It supports the extension of the interfaces since `vinyl` allows to add fields to the records.
diff --git a/vinyl-handle/domain/QueryTypes.hs b/vinyl-handle/domain/QueryTypes.hs
new file mode 100644 (file)
index 0000000..91e2183
--- /dev/null
@@ -0,0 +1,4 @@
+module QueryTypes where
+
+type Location = String
+type Day = String
diff --git a/vinyl-handle/domain/TemperatureProvider.hs b/vinyl-handle/domain/TemperatureProvider.hs
new file mode 100644 (file)
index 0000000..8a2eec5
--- /dev/null
@@ -0,0 +1,13 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module TemperatureProvider where
+
+import HandleRec
+import QueryTypes
+
+type Temperature = Int
+
+type Methods = '[ '("getTemperatureData", (Location -> Day -> IO Temperature)) ]
+
+type Handle = HandleRec Methods
index b974fca1e45904476b59c17f430601f358a08428..360861ca8f20d0042af21bf4b0c4480c21785e80 100644 (file)
@@ -1,16 +1,20 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeOperators #-}
 
 module WeatherProvider where
 
+import Data.Vinyl.TypeLevel
 import HandleRec
+import qualified WindProvider as W
+import qualified TemperatureProvider as T
+import QueryTypes
 
-type Temperature = Int
-data WeatherData = WeatherData { temperature :: Temperature }
+data WeatherData = WeatherData { temperature :: T.Temperature, wind :: W.WindSpeed }
 
-type Location = String
-type Day = String
+-- We union the methods of providers and extend it with a common method.
+type Methods = '[ '("getWeatherData", (Location -> Day -> IO WeatherData))
+  ] ++ W.Methods ++ T.Methods
+
+type Handle = HandleRec Methods
 
-type Handle = HandleRec
-  '[ '("getWeatherData", (Location -> Day -> IO WeatherData))
-  ]
index 343cc9145a4e996ce5ab8019dae3286ca84b705f..31a091667b42134050c1b2bb481db80b214a0439 100644 (file)
@@ -20,8 +20,9 @@ new = Handle
 
 -- | Domain logic. Usually some pure code that might use mtl, free monads, etc.
 createWeatherReport :: WeatherProvider.WeatherData -> WeatherReport
-createWeatherReport (WeatherProvider.WeatherData temp) =
+createWeatherReport (WeatherProvider.WeatherData temp wind) =
   "The current temperature in London is " ++ (show temp)
+  ++ " and wind speed is " ++ (show wind)
 
 -- | Domain logic that uses external dependency to get data and process it.
 getCurrentWeatherReportInLondon :: Handle -> IO WeatherReport
diff --git a/vinyl-handle/domain/WindProvider.hs b/vinyl-handle/domain/WindProvider.hs
new file mode 100644 (file)
index 0000000..bab1fb7
--- /dev/null
@@ -0,0 +1,14 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module WindProvider where
+
+import HandleRec
+import QueryTypes
+
+type WindSpeed = Int
+
+type Methods = '[ '("getWindData", (Location -> Day -> IO WindSpeed)) ]
+
+type Handle = HandleRec Methods
+
index b27871aa3f8d914f97baf0e1590278d33717c563..ea2b5753c9d28d6ad06159b13b9f3ea9941d444b 100644 (file)
@@ -2,11 +2,22 @@ module SuperWeatherProvider where
 
 import Data.Vinyl
 import WeatherProvider
+import TemperatureProvider (Temperature)
+import WindProvider (WindSpeed)
+import QueryTypes
 
 new :: Handle
 new = Field getSuperWeatherData
+  :& Field getWindData
+  :& Field getTemperatureData
   :& RNil
 
 -- | This is some concrete implementation `WeatherProvider` interface
 getSuperWeatherData :: Location -> Day -> IO WeatherData
-getSuperWeatherData _ _ = return $ WeatherData 30
+getSuperWeatherData _ _ = return $ WeatherData 30 10
+
+getTemperatureData :: Location -> Day -> IO Temperature
+getTemperatureData _ _ = return 30
+
+getWindData :: Location -> Day -> IO WindSpeed
+getWindData _ _ = return 5
index a31879e0705423d1aef93221b4fcd10b3a936103..f5ae0891386941ff8a38519bd7692996be0397e1 100644 (file)
@@ -2,16 +2,29 @@ module TestWeatherProvider where
 
 import Data.Vinyl
 import WeatherProvider
+import qualified TemperatureProvider as T
+import qualified WindProvider as W
+import QueryTypes
 
 -- | This is a configuration that allows to setup the provider for tests.
 data Config = Config
-  { initTemperature :: Temperature
+  { initTemperature :: T.Temperature
+  , initWindSpeed :: W.WindSpeed
   }
 
 new :: Config -> Handle
-new config = Field (getTestWeatherData $ initTemperature config)
+new config = Field (getTestWeatherData (initTemperature config) (initWindSpeed config))
+  :& Field (getWindData (initWindSpeed config))
+  :& Field (getTemperatureData (initTemperature config))
   :& RNil
 
 -- | This is an implementation `WeatherProvider` interface for tests
-getTestWeatherData :: Int -> Location -> Day -> IO WeatherData
-getTestWeatherData temp _ _ = return $ WeatherData temp
+-- We can configure it independently from other providers or reuse them.
+getTestWeatherData :: T.Temperature -> W.WindSpeed -> Location -> Day -> IO WeatherData
+getTestWeatherData temp wind _ _ = return $ WeatherData temp wind
+
+getTemperatureData :: T.Temperature -> Location -> Day -> IO T.Temperature
+getTemperatureData t _ _ = return t
+
+getWindData :: W.WindSpeed -> Location -> Day -> IO W.WindSpeed
+getWindData w _ _ = return w
index 7466dd49de053378980bd12ba1c103b9fda048f8..452ca213bc30e6a32345f19d4f083da0cfd2e161 100644 (file)
@@ -2,23 +2,28 @@ import Test.Hspec
 
 import qualified TestWeatherProvider
 import qualified WeatherProvider
+import qualified TemperatureProvider
+import qualified WindProvider
 import qualified WeatherReporter
 
 main :: IO ()
 main = hspec spec
 
-weatherWithTemp :: WeatherProvider.Temperature -> WeatherReporter.Handle
-weatherWithTemp = WeatherReporter.new
-  . TestWeatherProvider.new
-  . TestWeatherProvider.Config
+weatherWithTempAndWind
+  :: TemperatureProvider.Temperature
+  -> WindProvider.WindSpeed
+  -> WeatherReporter.Handle
+weatherWithTempAndWind t w = WeatherReporter.new
+  $ TestWeatherProvider.new
+  $ TestWeatherProvider.Config t w
 
 spec :: Spec
 spec = describe "WeatherReporter" $ do
-  it "weather in London is 0" $ do
+  it "weather in London is 0 and wind is 5" $ do
     weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon $
-      weatherWithTemp 0
-    weatherReportInLondon `shouldBe` "The current temperature in London is 0"
-  it "weather in London is -5" $ do
+      weatherWithTempAndWind 0 5
+    weatherReportInLondon `shouldBe` "The current temperature in London is 0 and wind speed is 5"
+  it "weather in London is -5 and wind is 10" $ do
     weatherReportInLondon <- WeatherReporter.getCurrentWeatherReportInLondon $
-      weatherWithTemp (-5)
-    weatherReportInLondon `shouldBe` "The current temperature in London is -5"
+      weatherWithTempAndWind (-5) 10
+    weatherReportInLondon `shouldBe` "The current temperature in London is -5 and wind speed is 10"
index e007df90ce1f5c824a7bc8a4c9724bf544562e21..c020c20446fe05f5a72f57b768828ec41e3233fa 100644 (file)
@@ -11,7 +11,10 @@ library domain
   hs-source-dirs: domain
   exposed-modules: WeatherProvider
                  , WeatherReporter
+                 , WindProvider
+                 , TemperatureProvider
                  , HandleRec
+                 , QueryTypes
   default-language: Haskell2010
   build-depends:    base, vinyl