在运行时选择实例行为

时间:2015-06-24 19:00:44

标签: haskell polymorphism

我试图在运行时从多个实例中选择一个实例。真的是一种Backend

如果我在编译时选择一个实例或其他实例,我就能做到。

UPDATED 可能我想要一些与Database.Persist类似的东西(它定义了一个完整的行为,但很多实例:mongodb,sqlite,postgresql,...)。但对我来说太复杂了。

更新使用GADTs有效,但我认为存在更好的方式(底部是完整代码)。

在一些OOP语言中,我的问题或多或少

interface IBehavior { void foo(); }

class AppObject { IBehavior bee; void run(); }

...
  var app = new AppObject { bee = makeOneOrOtherBehavior(); }
....

我尝试了很多方法(以及许多扩展:D),但都没有。

非正式地,我想要定义一个具有特定行为的class并将此通用定义用于某个应用程序,然后在运行时选择一个instance

通用行为(不是真实代码)

class Behavior k a where
  behavior :: k -> IO ()
  foo :: k -> a -> Bool
  ...

(我认为需要k,因为每个instance都需要自己的上下文/数据;其他限制可能会存在key / value

两个实例

data BehaviorA
instance Behavior BehaviorA where
  behavior _ = print "Behavior A!"

data BehaviorB
instance Behavior BehaviorB where
  behavior _ = print "Behavior B!"

我的应用程序使用该行为(此处开始混乱)

data WithBehavior =
  WithBehavior { foo :: String
               , bee :: forall b . Behavior b => b
               }

run :: WithBehavior -> IO ()
run (WithBehavior {..}) = print foo >> behavior bee

我希望在运行时选择

selectedBee x = case x of
                  "A" -> makeBehaviorA
                  "B" -> makeBehaviorB
                  ...
withBehavior x = makeWithBehavior (selectedBee x)

但是我迷失了一个扩展迷宫,输入依赖关系和其他:(

我无法为selectedBee功能设置正确的类型。

任何帮助将不胜感激! :)

(使用GADTs,但没有额外的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

2 个答案:

答案 0 :(得分:6)

为什么要使用类型类?相反,将类型类表示为记录类型,“实例”是该类型的值:

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

(我没有编译这段代码,但它应该可以工作)

要在编译时完全解析类型类。您试图强制它们在运行时被解析。相反,想想你是如何在OOP中真正指定它的:你有一个类型和一个函数,它根据它的参数返回该类型的某些值。然后,您调用该类型的方法。唯一的区别是,使用OOP解决方案,从选择函数返回的值不具有函数所说的确切类型,因此您返回BehaviorABehaviorB而不是IBehavior。使用Haskell,您必须实际返回与返回类型完全匹配的值。

OOP版本允许你做的唯一事情是Haskell没有把你的IBehavior强制转换回BehaviorABehaviorB,而这通常被认为是不安全的。如果您收到类型由接口指定的值,则应始终将自己限制为仅允许该接口允许的值。 Haskell强迫这一点,而OOP仅仅按惯例使用它。有关此模式的更完整说明,请查看this帖子。

答案 1 :(得分:4)

为什么要介绍这些类型BehaviorABehaviorB来发送?除非根据类型而不是值进行调度,否则它看起来像是Java的错误翻译;但它似乎在这里引起你的问题。

相反,如何放弃类型类并只使用“方法”记录?

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

(我不确定你对WithBehavior的确切意图是什么,因为你的Behavior课程在某处失去了两个论点中的一个。也许你想要一个普遍存在或存在量化的类型。 )