我一直在探索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
同样,我们可以进行切换,但是我并没有真正看到一些优雅的东西。我希望有几件事:
答案 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"