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

      

+3


source to share


2 answers


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.

+6


source


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.)

+4


source







All Articles