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.