Choosing instance behavior at runtime
I am stuck trying to select one instance from many at runtime. In fact, it's kind of Backend
.
I can do this by selecting one instance at compile time.
UPDATED Probably I need a little something similar to Database.Persist (it defines the complete behavior, but many instances: mongodb, sqlite, postgresql, ...). But this is too difficult for me.
UPDATED using GADTs
, but I think there is a better way (full code at the bottom).
In some OOP languages โโmy problem is more or less
interface IBehavior { void foo(); }
class AppObject { IBehavior bee; void run(); }
...
var app = new AppObject { bee = makeOneOrOtherBehavior(); }
....
I've tried many ways (and many extensions: D) but none seem to work.
Informally, I want to define one class
with a specific behavior and use that generic definition in some application, then select one instance
of a few at runtime .
General behavior (not real code)
class Behavior k a where
behavior :: k -> IO ()
foo :: k -> a -> Bool
...
(I think that's k
necessary, since each instance
might need their own context / data, there might be other constraints like key
/ value
)
Two copies
data BehaviorA
instance Behavior BehaviorA where
behavior _ = print "Behavior A!"
data BehaviorB
instance Behavior BehaviorB where
behavior _ = print "Behavior B!"
my app is using this behavior (here is the beginning of the chaos)
data WithBehavior =
WithBehavior { foo :: String
, bee :: forall b . Behavior b => b
}
run :: WithBehavior -> IO ()
run (WithBehavior {..}) = print foo >> behavior bee
It is advisable to select at runtime
selectedBee x = case x of
"A" -> makeBehaviorA
"B" -> makeBehaviorB
...
withBehavior x = makeWithBehavior (selectedBee x)
but I am lost in the maze of extensions, type dependencies and others :(
I am unable to set the correct type for the function selectedBee
.
Any help would be appreciated! :)
(Using GADTs
but without additional type parameters a
!)
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE GADTs #-}
import System.Environment
import Control.Applicative
class Behavior k where
behavior' :: k -> IO ()
data BehaviorInstance where
BehaviorInstance :: Behavior b => b -> BehaviorInstance
behavior :: BehaviorInstance -> IO ()
behavior (BehaviorInstance b) = behavior' b
data BehaviorA = BehaviorA
instance Behavior BehaviorA where
behavior' _ = print "Behavior A!"
makeBehaviorA :: BehaviorInstance
makeBehaviorA = BehaviorInstance BehaviorA
data BehaviorB = BehaviorB
instance Behavior BehaviorB where
behavior' _ = print "Behavior B!"
makeBehaviorB :: BehaviorInstance
makeBehaviorB = BehaviorInstance BehaviorB
data WithBehavior =
WithBehavior { foo :: String
, bee :: BehaviorInstance
}
run :: WithBehavior -> IO ()
run (WithBehavior {..}) = print foo >> behavior bee
main = do
n <- head <$> getArgs
let be = case n of
"A" -> makeBehaviorA
_ -> makeBehaviorB
run $ WithBehavior "Foo Message!" be
source to share
Why use a class? Instead, we represent a type type as a record type, with "instances" being values โโof that type:
data Behavior k a = Behavior
{ behavior :: IO ()
, foo :: k -> a -> Bool
}
behaviorA :: Behavior String Int
behaviorA = Behavior
{ behavior = putStrLn "Behavior A!"
, foo = \a b -> length a < b
}
behaviorB :: Behavior String Int
behaviorB = Behavior
{ behavior = putStrLn "Behavior B!"
, foo = \a b -> length a > b
}
selectBehavior :: String -> Maybe (Behavior String Int)
selectBehavior "A" = Just behaviorA
selectBehavior "B" = Just behaviorB
selectBehavior _ = Nothing
main :: IO ()
main = do
putStrLn "Which behavior (A or B)?"
selection <- getLine
let selected = selectBehavior selection
maybe (return ()) behavior selected
putStrLn "What is your name?"
name <- getLine
putStrLn "What is your age?"
age <- readLn -- Don't use in real code, you should actually parse things
maybe (return ()) (\bhvr -> print $ foo bhvr name age) selected
(I haven't compiled this code, but it should work)
Typeclasses must be fully resolved at compile time. You are trying to get them to be resolved at runtime. Instead, think about how you actually specify it in OOP: you have a type and a function that returns some value of that type based on its arguments. Then you call a method of this type. The only difference is that with the OOP solution, the values โโreturned by the select function do not have the exact type that the function says it should, so you return BehaviorA
or BehaviorB
instead IBehavior
. With Haskell, you should actually return a value that exactly matches the return type.
The only thing the OOP version allows you to do is that Haskell does not return a tag IBehavior
in BehaviorA
or BehaviorB
, and this is often considered unsafe. If you get a value whose type is given by an interface, you should always limit yourself to only what the interface allows. Haskell forces this, while OOP only uses it by convention. For a more complete explanation of this pattern, check out this post.
source to share
Why did you enter these types BehaviorA
, BehaviorB
to send? This looks like a bad translation from Java, unless there is some definite advantage to type-based dispatching rather than value-based dispatch; but it seems to be causing you problems.
Instead, how about dropping the type class and just using the "methods" notation?
data Behavior a = Behavior { behavior :: IO (), ... }
behaviorA = Behavior { behavior = print "Behavior A!" }
behaviorB = Behavior { behavior = print "Behavior B!" }
selectedBee x = case x of
"A" -> behaviorA
"B" -> behaviorB
data WithBehavior a = WithBehavior { foo :: String
, bee :: Behavior a }
run :: WithBehavior a -> IO ()
run (WithBehavior {..}) = print foo >> behavior bee
(I'm not sure exactly what you intended with WithBehavior
, since your class Behavior
lost one of its two arguments somewhere along the way. You might want a generic or existential quantifiable type.)
source to share