简体   繁体   中英

Passing runtime information for polymorphic function

This is follow-up to my previous question about type-indexed map. Following the discussion in the comments, I am posting the actual problem here to see if there is a neat way to solve this using dependent-type programming.

The problem is that given run-time information that we need for a typeclass function - we have different types of messages coming over the wire, and we use that function to do message specific processing using the run-time configuration - how do we pass run-time information (one run-time configuration for each type instance) to that function?

Toy code below with comments - we use typeclass function f within g which gets runtime information and applies it to f - btw, the set of message types a is fixed - so, we can use closed typefamilies if need be:

module Main where

main :: IO ()
main = do
  let runtimeinfo = Mesg { aString = "someheader"}
      builder = (\x -> Mesg { aString = (aString runtimeinfo) ++ (aString x)})  
  -- should call function "app" on this line which will call function "g"
  return () 

data Mesg = Mesg {aString :: String} deriving Show

class Process a where
  f :: a -> a -- FYI, in actual app, output type is (StateT a IO Builder)
  -- We can't define builder below at compile-time because it is created at run-time in main above
--builder :: a -> a 

instance Process Mesg where
  f inp = Mesg { aString = (reverse (aString inp))} -- contrived example - a placeholder for some processing on message

-- g is not directly reachable from main - main calls a function "app" which 
-- calls g (after receiving "inp" of type "a" over the wire) - so, to pass 
-- builder, we have to pass it along. builder is from runtime configuration - 
-- in this example, it is created in main. If it were part of typeclass Process,
-- we won't need to pass it along
g :: Process a => (a -> a) -> a -> a
g builder inp = builder $ f inp -- we call processing function f here with runtime builder

-- Alternative approach pseudo code - map here is created in main, and passed to g via app
{--
 g :: (Process a, Typeable a) => Map String Dynamic -> a -> Maybe a
 g map inp = (retrieve corresponding builder from map using type-indexed string), apply here with f
--}

My solution so far is type-indexed map in g which looks up builder for the type a .

This question really isn't self-contained, but it sounds like you might be able to use the facilities in the reflection package. In particular, it lets you use runtime information in typeclass instances. For example, you could write something like this (untested).

{-# LANGUAGE ScopedTypeVariables,
      UndecidableInstances, .... #-}
import Data.Proxy
import Data.Reflection

data Configuration a = Configuration
  { the_builder :: a -> a
  , someOtherThing :: Int
  , whatever :: Char }

class Buildable a where
  builder :: a -> a

newtype Yeah s a = Yeah a

instance Reifies s (Configuration a) =>
            Buildable (Yeah s a) where
  builder (Yeah x) = Yeah $ the_builder (reflect (Proxy :: Proxy s)) x

Then you can write

reify config $ \(_ :: Proxy s) -> expr

and within expr , the type Yeah sa will be an instance of the Buildable class.

If you're able to change the declaration of builder , why not do it the old-fashioned way and just plumb in the config using a monad?

data Config = Config {
    theHeader :: String,
    somethingElse :: Int,
    andAnotherThing :: Bool
}

class Buildable a where
    build :: MonadReader Config m => String -> m a


data Msg = Msg { msg :: String } deriving (Show)

instance Buildable Msg where
    build body = do
        config <- ask
        return $ Msg { msg = theHeader config ++ body }


-- imagine we're getting this from a file in the IO monad
readConfigFile = return $ Config {
    theHeader = "foo",
    somethingElse = 4,
    andAnotherThing = False
}
-- imagine we're reading this from the wire
getFromSocket = return "bar"

main = do
    config <- readConfigFile
    body <- getFromSocket
    msg <- runReaderT (build body) config
    print msg

@dfeuer's reflection answer becomes useful if you don't own the class and can't add the monadic context to build 's type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM