I've been exploring Haskell to get some functional programming experience. I'm hoping to find patterns that bring some insight so I can write better c++. For instance, I find the normal implementation of converting a switch statement to polymorphic code unsatisfying.
void HandleMessage(int type, Message m)
{
switch (type)
{
case 1:
HandleType1(m);
break;
case 2:
HandleType2(m);
break;
default:
Log("error, unhandled message type")
}
}
becomes:
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>();
}
}
This just pushes the switch to the factory. It's probably better than the first solution, but it just feels like there should be something better.
I was wondering if a better pattern exists in Haskell. I know you can use guards:
handleMessage t msg
| t == 1 = handleType1 msg
| t == 2 = handleType2 msg
But this doesn't seem that much more elegant. You could switch the types as ints to a proper type and do pattern matching:
data Type = Type1 | Type2
createType t
| t == 1 = Type1
| t == 2 = Type2
handleMessage Type1 msg = handleType1 msg
handleMessage Type2 msg = handleType2 msg
Again, we can push the switch around, but I'm not really seeing something elegant. I'm hoping for several things:
Your Haskell solutulion is a near copy of the first C++ fragment. It only looks better because of Haskell's terser syntax, but it does exactly the same thing. Actually an exact C++ equivalent would be
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
As you can see there isn't too much of wonderful and exciting functional programming going on. Indeed, functional programming is about programming with functions as first class values . There ain't any in your example.
Let's change it a bit.
....
handler Type1 = handleType1
handler Type2 = handleType2
So now handler
is a primitive higher-order function. It gets a Type and returns another function. This still isn't much, but this translates to C++ this way:
void handleType1(Message);
void handleType2(Message);
auto getHandler(Type type) {
switch(type) {
case Type1: return handleType1;
case Type2: return handleType2;
....
So instead of returning an object, you return a function. This is not a coimcidence. An object is just a bunch of functions (its methods). An object with one method is just a function!
Wait, what about data? Isn't an object made up of methods and data ? It is, but its data can be bound in a function (think lambda or std::bind). Normal C++ functions (function pointers) cannot capture context, but you just use lambdas and std::function.
So I guess this example doesn't bring too much insight to the table in the end. Instead of objects,use functions, that's all.
You can do pattern matching on 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...
But if you don't want to do that, you can directly do pattern matching on Types:
handleType Type1 msg = handle1 msg
handleType Type2 msg = handle2 msg
But even better would be using a type class:
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"
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.