haskell中用多态替换条件语句的等效模式是什么?

时间:2019-07-08 20:20:31

标签: c++ haskell

我一直在探索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>();
   }
}

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

我想知道Haskell是否存在更好的模式。我知道您可以使用警卫:

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

但这似乎没有那么优雅。您可以将类型从int切换为适当的类型并进行模式匹配:

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. 很高兴:缺少特定类型的处理程序将导致编译错误。

2 个答案:

答案 0 :(得分:2)

您的Haskell解决方案是第一个C ++片段的近似副本。由于Haskell的terser语法,它看起来更好,但是它做的完全一样。实际上,确切的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。

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

答案 1 :(得分:0)

您可以在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"