class: center, middle # Contravariant Functor:
brilliant
idea --- ## Functor ```haskell class Functor f where fmap :: (a -> b) -> f a -> f b ``` #### `fmap` applies `(a -> b)` to a value `a` and produces a new value `b` (in the same context `f`) --- ## Functor ```haskell class Functor f where fmap :: (a -> b) -> f a -> f b ``` #### `fmap` applies `(a -> b)` to a value `a` and produces a new value `b` (in the same context `f`) #### Example ```haskell let initValue :: Maybe Int initValue = Just 3 times10 :: Int -> Int times10 i = i * 10 newValue :: Maybe Int newValue = fmap times10 initValue -- newValue is Just 30 ``` --- ## Contravariant Functor ```haskell class Contravariant f where contramap :: (a -> b) -> f b -> f a ``` ## Functor ```haskell class Functor f where fmap :: (a -> b) -> f a -> f b ``` --- ## Contravariant Functor ```haskell class Contravariant f where contramap :: (a -> b) -> f b -> f a ``` ## Functor ```haskell class Functor f where fmap :: (a -> b) -> f a -> f b ``` ## Let's compare ```haskell fmap :: (a -> b) -> f a -> f b -- we can apply (a -> b) to f a contramap :: (a -> b) -> f b -> f a -- but we cannot apply (a -> b) to f b ``` --- ## Stringifier ```haskell newtype Stringifier a = Stringifier { runStringifier :: a -> String } ``` --- ## Stringifier ```haskell newtype Stringifier a = Stringifier { runStringifier :: a -> String } ``` #### Example: ```haskell intStringifier :: Stringifier Int intStringifier = Stringifier show ``` --- ## Stringifier ```haskell newtype Stringifier a = Stringifier { runStringifier :: a -> String } ``` #### Example: ```haskell intStringifier :: Stringifier Int intStringifier = Stringifier show ``` #### Let's make stringifier a contravariant functor: ```haskell instance Contravariant Stringifier where contramap f s = Stringifier (runStringifier s . f) ``` --- ## Usage ```haskell newtype Stringifier a = Stringifier { runStringifier :: a -> String } instance Contravariant Stringifier where contramap f s = Stringifier (runStringifier s . f) newtype MyNumber = MyNumber Int times10 :: MyNumber -> Int times10 (MyNumber i) = i * 10 main :: IO () main = do let intStringifier :: Stringifier Int intStringifier = Stringifier show myNumberStringifier :: Stringifier MyNumber myNumberStringifier = contramap times10 intStringifier result = runStringifier myNumberStringifier $ MyNumber 3 putStrLn result ``` --- ## How it works? ```haskell contramap f s = Stringifier (runStringifier s . f) ``` --- ## How it works? ```haskell contramap f s = Stringifier (runStringifier s . f) ``` #### With our values: ```haskell contramap times10 intStringifier = Stringifier (runStringifier intStringifier . times10) ```
┗━━━━━━━━━━━━━━━━━━━━┛
┗━━━━┛ #####
`Int -> String`
`MyNumber -> Int` --- ## How it works? ```haskell contramap f s = Stringifier (runStringifier s . f) ``` #### With our values: ```haskell contramap times10 intStringifier = Stringifier (runStringifier intStringifier . times10) ```
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ #####
`MyNumber -> String` --- ## How it works? ```haskell contramap f s = Stringifier (runStringifier s . f) ``` #### With our values: ```haskell contramap times10 intStringifier = Stringifier (runStringifier intStringifier . times10) ```
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ #####
`MyNumber -> String` ```haskell class Contravariant f where contramap :: (a -> b) -> f b -> f a ``` #### `contramap` composes `(a -> b)` with a
consumer
`b` and creates a new
consumer
`a` (in the same context `f`) --- class: center, middle ## `contramap` builds new consumers! --- ## Extended stringifier ```haskell newtype Stringifier m a = Stringifier { runStringifier :: a -> m () } ``` #### Consumes value of type `a` and processes it in some monadic context `m` --- ## Extended stringifier ```haskell newtype Stringifier m a = Stringifier { runStringifier :: a -> m () } ``` #### Consumes value of type `a` and processes it in some monadic context `m` #### Old instance ```haskell instance Contravariant Stringifier where contramap f s = Stringifier (runStringifier s . f) ``` #### New instance ```haskell instance Contravariant (Stringifier m) where contramap f s = Stringifier (runStringifier s . f) ``` #### We just have to keep functions
composable
--- ## Extended example ```haskell newtype Stringifier m a = Stringifier { runStringifier :: a -> m () } instance Contravariant (Stringifier m) where contramap f s = Stringifier (runStringifier s . f) newtype MyNumber = MyNumber Int times10 :: MyNumber -> Int times10 (MyNumber i) = i * 10 main :: IO () main = do let intStringifier :: Stringifier IO Int intStringifier = Stringifier print myNumberStringifier :: Stringifier IO MyNumber myNumberStringifier = contramap times10 intStringifier runStringifier myNumberStringifier $ MyNumber 3 ``` #### Both `intStringifier` and `myNumberStringifier` work in `IO`, but consume values of different types --- ## Idea ### `Functor` is about creating of new values ### `Contravariant` is about creating of new
consumers
of values --- class: center, middle ## Real Life example --- ## Tracer ```haskell newtype Tracer m a = Tracer { runTracer :: a -> m () } instance Contravariant (Tracer m) where contramap f (Tracer t) = Tracer (t . f) ``` --- ## Usage ```haskell main :: IO () main = do c <- defaultConfigStdout tracer :: Tracer IO (LogObject String) <- setupTrace (Right c) "simple" logInfo tracer "string info message" ``` #### Output: .terminal[ [iohk.simple:Info:ThreadId 4] [2019-04-03 21:37:24.66 UTC] "string info message" ] --- ## Usage ```haskell data MyMessage = MyMessage { ss :: String, ii :: Int } deriving (Generic, Show, Eq, ToJSON) converter :: LogObject MyMessage -> LogObject String converter (LogObject nm me (LogMessage c)) = LogObject nm me (LogMessage (show c)) main :: IO () main = do c <- defaultConfigStdout tracer1 :: Tracer IO (LogObject String) <- setupTrace (Right c) "simple" let tracer2 :: Tracer IO (LogObject MyMessage) tracer2 = contramap converter tracer1 logInfo tracer1 "string info message" logInfo tracer2 (MyMessage "my info message" 123) ``` #### Output: .terminal[ [iohk.simple:Info:ThreadId 4] [2019-04-03 21:37:24.66 UTC] "string info message"
[iohk.simple:Info:ThreadId 4] [2019-04-03 21:37:24.66 UTC] "MyMessage {ss = \"my info message\", ii = 123}" ] --- ## Usage .smaller-code[ ```haskell data MyMessage = MyMessage { ss :: String, ii :: Int } deriving (Generic, Show, Eq, ToJSON) converter :: LogObject MyMessage -> LogObject String converter (LogObject nm me (LogMessage c)) = LogObject nm me (LogMessage (show c)) fromStringToMyMessage :: (LogObject MyMessage -> LogObject String) -> Tracer IO (LogObject String) -> Tracer IO (LogObject MyMessage) fromStringToMyMessage = contramap transformer :: Tracer IO (LogObject String) -> Tracer IO (LogObject MyMessage) transformer = fromStringToMyMessage converter main :: IO () main = do c <- defaultConfigStdout tracer1 :: Tracer IO (LogObject String) <- setupTrace (Right c) "simple" let tracer2 :: Tracer IO (LogObject MyMessage) = contramap converter tracer1 let tracer3 :: Tracer IO (LogObject MyMessage) = transformer tracer1 logInfo tracer1 "string info message" logDebug tracer2 (MyMessage "my debug message" 123) logWarning tracer3 (MyMessage "my warning message" 456) ``` ] #### Output: .terminal[ [iohk.simple:Info:ThreadId 4] [2019-04-03 21:37:24.66 UTC] "string info message"
[iohk.simple:Debug:ThreadId 4] [2019-04-03 21:37:24.66 UTC] "MyMessage {ss = \"my debug message\", ii = 123}"
[iohk.simple:Warning:ThreadId 4] [2019-04-03 21:37:24.66 UTC] "MyMessage {ss = \"my warning message\", ii = 456}" ] --- ## Tracer idea #### 1. Build the first tracer. #### 2. Build the next tracer based on the first one: take existing context and
inject
some new info in it. #### 3. And so on... --- class: center, middle ## Thank you!