Haskell中用多态性替换条件的等效模式是什么?

What is the equivalent pattern in haskell for replacing conditionals with polymorphism?

本文关键字:模式 是什么 条件 多态性 替换 Haskell      更新时间:2023-10-16

我一直在探索Haskell以获得一些函数式编程经验。我希望找到能带来一些见解的模式,这样我就可以写出更好的 c++。例如,我发现将 switch 语句转换为多态代码的正常实现并不令人满意。

void HandleMessage(int type, Message m)
{
switch (type)
{
case 1:
HandleType1(m);
break;
case 2:
HandleType2(m);
break;
default:
Log("error, unhandled message type")
}
}

成为:

void HandleMessage(Type t, Message m)
{
std::unique_ptr<HandlerInterface> handler = HandlerFactory.GetHandler(t);
handler.handleMessage(m);
}
std::unique_ptr<HandlerInterface> HandlerFactory::GetHandler(Type t)
{
switch (t)
{
case 1:
return std::make_unique<HandlerType1>();
case 2:
return std::make_unique<HandlerType2>();
default:
return std::make_unique<DefaultHandler>();
}
}

这只是将开关推到工厂。它可能比第一个解决方案更好,但感觉应该有更好的解决方案。

我想知道哈斯克尔是否存在更好的模式。我知道你可以使用警卫:

handleMessage t msg
| t == 1 = handleType1 msg
| t == 2 = handleType2 msg

但这似乎并没有那么优雅。您可以将类型作为整数切换到正确的类型并进行模式匹配:

data Type = Type1 | Type2
createType t
| t == 1 = Type1
| t == 2 = Type2
handleMessage Type1 msg = handleType1 msg
handleMessage Type2 msg = handleType2 msg

同样,我们可以按下开关,但我并没有真正看到优雅的东西。我希望有几件事:

  1. 凝聚力:代码很好地捆绑在一起。
  2. 添加新句柄几乎不需要样板。没有一个是首选!
  3. 很高兴拥有:缺少特定类型的处理程序将是一个编译错误。

你的 Haskell 解决方案是第一个C++片段的近似副本。它只是因为 Haskell 的更简洁的语法而看起来更好,但它做同样的事情。实际上,一个确切的C++等价物是

enum Type { Type1, Type2 };
...
Type getType(int type) {
switch (type) {
case 1: return Type1;
case 2: return Type2;
...
...
switch (getType(type)) {
case Type1: // etc etc

正如你所看到的,没有太多精彩和令人兴奋的函数式编程正在进行。事实上,函数式编程是关于以函数作为第一类值的编程。您的示例中没有任何。

让我们稍微改变一下。

....
handler Type1 = handleType1
handler Type2 = handleType2

所以现在handler是一个原始的高阶函数。它获取一个 Type 并返回另一个函数。这仍然不多,但这转化为这样C++:

void handleType1(Message);
void handleType2(Message);
auto getHandler(Type type) {
switch(type) {
case Type1: return handleType1;
case Type2: return handleType2;
....

因此,您不是返回一个对象,而是返回一个函数。这不是巧合。对象只是一堆函数(它的方法)。具有一个方法的对象只是一个函数!

等等,数据呢?对象不是由方法和数据组成的吗?它是,但它的数据可以绑定在一个函数中(想想lambda或std::bind)。普通C++函数(函数指针)无法捕获上下文,但您只需使用 lambda 和 std::function。

所以我想这个例子最终不会带来太多的见解。代替对象,使用函数,仅此而已。

您可以在 Int 上进行模式匹配:

createType 1 = Type1
createType 2 = Type2
createType _ = error("error, unhandled message type")
handleMessage n msg = handleType (createType n) msg
handleType Type1 msg = doSome...
handleType Type2 msg = doSome2...

但是如果你不想这样做,你可以直接对 Types 进行模式匹配:

handleType Type1 msg = handle1 msg
handleType Type2 msg = handle2 msg

但更好的是使用类型类:

class Handler a where
handleMsg :: a -> String -> IO ()

data Type = Type1 | Type2
instance Handler Type where
handleMsg Type1 = handle1
handleMsg Type2 = handle2
handle1 msg = putStrLn $ "handle1 " ++ msg
handle2 msg = putStrLn $ "handle2 " ++ msg 
main = do
handleMsg Type1 "error 1"
handleMsg Type2 "error 2"