我试图在运行时从多个实例中选择一个实例。真的是一种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
答案 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解决方案,从选择函数返回的值不具有函数所说的确切类型,因此您返回BehaviorA
或BehaviorB
而不是IBehavior
。使用Haskell,您必须实际返回与返回类型完全匹配的值。
OOP版本允许你做的唯一事情是Haskell没有把你的IBehavior
强制转换回BehaviorA
或BehaviorB
,而这通常被认为是不安全的。如果您收到类型由接口指定的值,则应始终将自己限制为仅允许该接口允许的值。 Haskell强迫这一点,而OOP仅仅按惯例使用它。有关此模式的更完整说明,请查看this帖子。
答案 1 :(得分:4)
为什么要介绍这些类型BehaviorA
,BehaviorB
来发送?除非根据类型而不是值进行调度,否则它看起来像是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
课程在某处失去了两个论点中的一个。也许你想要一个普遍存在或存在量化的类型。 )