2015-06-24 92 views
3

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

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

修订大概我想有些类似于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 
    ... 

(我认为因为每个instancek需要可能需要自己的上下文/数据;像其他限制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

我认为你最好的选择可能是'reflection'包(不要用'Given'来保存你的理智)。 – dfeuer

+0

我得到'WithBehavior'的类型是'String - > WithBehavior'。 'withBehavior'的类型是什么?什么是'makeWithBehavior'?它最终崩溃了,意图操作落入未定义和未定义的符号。 –

+0

@dfeuer使用'data BehaviorWrapper其中{BehaviorWrapper :: Behavior b => b - > BehaviorWrapper}'可以做,但我想避免类型包装:) – josejuan

回答

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,而且这通常被认为是不安全的。如果您收到的类型是由接口指定的值,则应始终将自己限制为仅允许该接口允许的值。哈斯克尔强制这一点,而OOP仅仅通过惯例来使用它。有关此模式的更完整说明,请查看this后。

+0

是的@bheklilr我知道这些直接OOP aproach(和工作!)它记住我C风格的OOP模式:D但是你'k'是相同类型('String')和'selectBehavior '只返回一个固定类型(然后,''''''也可以) – josejuan

+0

@josejuan正确的,如果你需要它更灵活,那么你将不得不应用其他技术,我包括那些数据类型参数,因为你有他们在您的示例代码的某些地方,但不在其他地方。你必须在那里返回完全相同的类型,这是一个限制,但不是那么糟糕,有一些限制。但是,这里的主要问题来自于试图直接使用类型类将OOP转换为FP,而这不是类型类所特有的。 – bheklilr

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类失去了它的两个参数一个沿途某处,也许你想要一个普遍的或存在性量化类型来代替。)

+0

对不起@ReidBarton(和其他人)是我的错(我解释得不好)。每个行为实例(备选)都有自己的上下文(数据类型)。 'Database.Persist'可能是我正在寻找的一个很好的例子,但对我来说太复杂了。 – josejuan

+0

这实际上并不重要。如果需要的话,可以将类型参数添加到“行为”中,或者只是在实现“行为”时使用上下文,使其不会出现在类型中。类型类提供了方便性和一致性,当你甚至不知道如何编写程序时,这两者都不相关。 –

+0

@josejuan,比在编辑中使用GADT的版本更好的方法是在我的回答或bheklilr的回答中的版本(删除了“行为”的类型参数,因为您不使用它们)。它们是等价的。 –