在运行时选择实例行为

jos*_*uan 3 polymorphism haskell

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

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

更新可能我想要一些类似于Database.Persist(它定义一个完全行为,但很多实例:mongodb,sqlite,postgresql,...).但对我来说太复杂了.

使用GADTs作品更新,但我认为存在更好的方式(底部的完整代码).

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

interface IBehavior { void foo(); }

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

...
  var app = new AppObject { bee = makeOneOrOtherBehavior(); }
....
Run Code Online (Sandbox Code Playgroud)

我尝试了很多方法(以及很多扩展:D)但没有一种方法可行.

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

通用行为(不是真正的代码)

class Behavior k a where
  behavior :: k -> IO ()
  foo :: k -> a -> Bool
  ...
Run Code Online (Sandbox Code Playgroud)

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

两个例子

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

data BehaviorB
instance Behavior BehaviorB where
  behavior _ = print "Behavior B!"
Run Code Online (Sandbox Code Playgroud)

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

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

run :: WithBehavior -> IO ()
run (WithBehavior {..}) = print foo >> behavior bee
Run Code Online (Sandbox Code Playgroud)

我希望在运行时选择

selectedBee x = case x of
                  "A" -> makeBehaviorA
                  "B" -> makeBehaviorB
                  ...
withBehavior x = makeWithBehavior (selectedBee x)
Run Code Online (Sandbox Code Playgroud)

但我迷失了迷宫的扩展,类型依赖和其他:(

我无法为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
Run Code Online (Sandbox Code Playgroud)

bhe*_*ilr 6

为什么要使用类型类?相反,将类型类表示为记录类型,"instances"是该类型的值:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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