danidiaz / dep-t

Dependency injection for records-of-functions.
http://hackage.haskell.org/package/dep-t
BSD 3-Clause "New" or "Revised" License
8 stars 2 forks source link

Efficiency concerns #37

Open danidiaz opened 1 year ago

danidiaz commented 1 year ago

See responses to this tweet

With this code:

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveAnyClass #-}
module Main where

import Foo
import Logger
import Dep.Has
import Dep.Env ( Identity(..), Phased )

import GHC.Generics (Generic)
import Data.Functor.Identity
import Control.Arrow
import Dep.Constructor

type Deps m = Deps_ Identity m

data Deps_ h m = Deps {
    foo :: h (Foo m),
    logger :: h (Logger m)
} deriving stock Generic
  deriving anyclass Phased

instance Has Foo m (Deps m) where
    dep Deps { foo}= runIdentity foo

instance Has Logger m (Deps m) where
    dep Deps { logger}= runIdentity logger

deps_ :: Deps_ (Constructor (Deps IO)) IO
deps_ = Deps {
        logger = arr $ \_ -> makeIOLogger,
        foo = arr $ \deps -> makeFoo deps
    }

deps :: Deps IO 
deps = fixEnv deps_

main :: IO ()
main = do
   doFoo (dep deps) 10 "foo"  
module Logger where

newtype Logger m = Logger { doLog :: String -> m () }

makeIOLogger :: Logger IO 
makeIOLogger = Logger { doLog = putStrLn }
{-# LANGUAGE BlockArguments #-}
module Foo where

import Dep.Has
import Logger

newtype Foo m = Foo { doFoo :: Int -> String -> m () }

makeFoo :: (Has Logger m deps, Monad m) => deps -> Foo m
makeFoo (Call call)= Foo \_ msg -> do
    call doLog (msg ++ "1")
    call doLog (msg ++ "2")
    call doLog (msg ++ "3")

The core for makeFoo is:

$wmakeFoo
  :: forall {m :: * -> *} {deps}.
     Has Logger m deps =>
     (forall a b. m a -> m b -> m b) -> deps -> String -> m ()
$wmakeFoo
  = \ (@(m :: * -> *))
      (@deps)
      (w :: Has Logger m deps)
      (ww :: forall a b. m a -> m b -> m b)
      (w1 :: deps)
      (w2 :: String) ->
      ww
        ((((w `cast` <Co:4>) w1) `cast` <Co:2>) (++ w2 makeFoo6))
        (ww
           ((((w `cast` <Co:4>) w1) `cast` <Co:2>) (++ w2 makeFoo4))
           ((((w `cast` <Co:4>) w1) `cast` <Co:2>) (++ w2 makeFoo2)))

The method is extracted (w) from the dependency context (w1) three times. That might be costly?

Perhaps we could limit ourselves with positional parameters in constructors:

makeFoo :: Monad m => Logger m -> Foo m
makeFoo logger = Foo \_ msg -> do
    doLog logger (msg ++ "1")
    doLog logger (msg ++ "2")
    doLog logger (msg ++ "3")

Now the core is:

$wmakeFoo
  :: forall {m :: * -> *}.
     (forall a b. m a -> m b -> m b) -> Logger m -> String -> m ()
$wmakeFoo
  = \ (@(m :: * -> *))
      (ww :: forall a b. m a -> m b -> m b)
      (w :: Logger m)
      (w1 :: String) ->
      ww
        ((w `cast` <Co:2>) (++ w1 makeFoo6))
        (ww
           ((w `cast` <Co:2>) (++ w1 makeFoo4))
           ((w `cast` <Co:2>) (++ w1 makeFoo2)))