Select 实例在运行时的行为
Select instance behavior at runtime
我一直在尝试 select 在运行时从多个实例中选择一个实例。真的是一种Backend
.
如果我在编译时 select 一个实例或其他实例,我就能做到。
UPDATED 可能我想要一些类似于 Database.Persist 的东西(它定义了一个完整的行为,但有很多实例:mongodb、sqlite、postgresql、... ).但是对我来说太复杂了。
UPDATED 使用 GADTs
可行,但我认为存在更好的方法(底部的完整代码)。
在某些 OOP 语言中我的问题或多或少
interface IBehavior { void foo(); }
class AppObject { IBehavior bee; void run(); }
...
var app = new AppObject { bee = makeOneOrOtherBehavior(); }
....
我尝试了很多方法(还有很多扩展 :D)但是 none 有效。
非正式地,我想定义一个具有特定行为的 class
并将此通用定义用于某些应用程序,之后,select 在运行时 instance
来自一些。
通用行为(不是真正的代码)
class Behavior k a where
behavior :: k -> IO ()
foo :: k -> a -> Bool
...
(我认为 k
是必需的,因为每个 instance
可能需要自己的 context/data;可能存在 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
我希望在运行时select
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
为什么要引入这些类型 BehaviorA
、BehaviorB
来调度?它看起来像是来自 Java 的糟糕翻译,除非基于类型而不是值进行调度有一些特定的优势;但这似乎给你带来了麻烦。
相反,放弃类型 class 并只使用 "methods" 的记录怎么样?
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
class 在此过程中的某个地方丢失了它的两个参数之一。也许您想要一个通用的或存在量化类型。)
为什么要使用类型类?相反,将类型类表示为记录类型,"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
(我没有编译这段代码,但应该可以)
类型类应在编译时完全解析。您正试图强制它们在运行时得到解决。相反,请考虑如何在 OOP 中真正指定它:您有一个类型和一个函数,return 是该类型基于其参数的某些值。然后您对该类型调用一个方法。唯一的区别是,对于 OOP 解决方案,从选择函数中编辑的值 return 没有函数所说的确切类型,所以你 returning a BehaviorA
或 BehaviorB
而不是 IBehavior
。使用 Haskell 你实际上必须 return 一个与 return 类型完全匹配的值。
OOP 版本允许您做的唯一一件事 Haskell 不允许您将 IBehavior
转换回 BehaviorA
或 BehaviorB
,这是无论如何通常被认为是不安全的。如果您收到一个值,其类型由接口指定,您应该始终将自己限制在该接口允许的范围内。 Haskell 强制这样做,而 OOP 仅按惯例使用它。有关此模式的更完整解释,请查看 this post.
我一直在尝试 select 在运行时从多个实例中选择一个实例。真的是一种Backend
.
如果我在编译时 select 一个实例或其他实例,我就能做到。
UPDATED 可能我想要一些类似于 Database.Persist 的东西(它定义了一个完整的行为,但有很多实例:mongodb、sqlite、postgresql、... ).但是对我来说太复杂了。
UPDATED 使用 GADTs
可行,但我认为存在更好的方法(底部的完整代码)。
在某些 OOP 语言中我的问题或多或少
interface IBehavior { void foo(); }
class AppObject { IBehavior bee; void run(); }
...
var app = new AppObject { bee = makeOneOrOtherBehavior(); }
....
我尝试了很多方法(还有很多扩展 :D)但是 none 有效。
非正式地,我想定义一个具有特定行为的 class
并将此通用定义用于某些应用程序,之后,select 在运行时 instance
来自一些。
通用行为(不是真正的代码)
class Behavior k a where
behavior :: k -> IO ()
foo :: k -> a -> Bool
...
(我认为 k
是必需的,因为每个 instance
可能需要自己的 context/data;可能存在 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
我希望在运行时select
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
为什么要引入这些类型 BehaviorA
、BehaviorB
来调度?它看起来像是来自 Java 的糟糕翻译,除非基于类型而不是值进行调度有一些特定的优势;但这似乎给你带来了麻烦。
相反,放弃类型 class 并只使用 "methods" 的记录怎么样?
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
class 在此过程中的某个地方丢失了它的两个参数之一。也许您想要一个通用的或存在量化类型。)
为什么要使用类型类?相反,将类型类表示为记录类型,"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
(我没有编译这段代码,但应该可以)
类型类应在编译时完全解析。您正试图强制它们在运行时得到解决。相反,请考虑如何在 OOP 中真正指定它:您有一个类型和一个函数,return 是该类型基于其参数的某些值。然后您对该类型调用一个方法。唯一的区别是,对于 OOP 解决方案,从选择函数中编辑的值 return 没有函数所说的确切类型,所以你 returning a BehaviorA
或 BehaviorB
而不是 IBehavior
。使用 Haskell 你实际上必须 return 一个与 return 类型完全匹配的值。
OOP 版本允许您做的唯一一件事 Haskell 不允许您将 IBehavior
转换回 BehaviorA
或 BehaviorB
,这是无论如何通常被认为是不安全的。如果您收到一个值,其类型由接口指定,您应该始终将自己限制在该接口允许的范围内。 Haskell 强制这样做,而 OOP 仅按惯例使用它。有关此模式的更完整解释,请查看 this post.