The usual way to "match patterns" by type in the way you describe is with type instances. With specific types, this is easy to use with MultiParamTypeClasses ; this is how Haskell implements multiple dispatchers.
{-
This works great. This is more or less idiomatic Haskell, except that (**) and (^) are different operations, and some people may object to blurring the difference.
However, you are asking for something more complex. You want to send multiple times not only by type, but also by type class. This is a much different and more powerful thing. In particular, it will work for all types that Floating or Intergral instances can have, even types that have not yet been written! Here's how it would be written perfectly:
instance (Floating a) => Power aa where my_pow = (**) instance (Num a, Integral b) => Power ab where my_pow = (^)
This does not work , however, since the constraint resolver does not back down and does not take into account the constraints of the instance when selecting the instance. Thus, my_pow does not work, for example, with two Int s:
ghci> :t my_pow :: Int -> Int -> Int No instance for (Floating Int)
This is because there is a “more specific" Power aa instance, since both types are equal. The GHC then imposes a Floating constraint on a and barfs when it cannot satisfy it. Then it does not back down and does not try to execute an instance of Power ab .
It may or may not be possible to get around the restriction using extended functions of a system like, but I don’t think you could make a replacement for a replacement of both (**) and (^) in the current Haskell.
Edit: general comments
(Please note that we are distracting from the Q & A format here.)
Rereading my question and commentary, I notice that you use the term “sending” in a way that I am not familiar with. Quick Google opens up dual-post articles and a visitor design template . Where are you from? They are a bit like what you are trying to do - write a function that does completely different things based on the types of its arguments. I want to add a few things to this answer, which can help hone your sense of idiomatic Haskell. (Or maybe just disconnected incoherent.)
Haskell usually ignores the idea of "type runtime". Even in @Cirdec’s more complex answer, all types are statically known "at compile time". (Using REPL, ghci does not change anything except that the “compilation time” becomes a bit foggy.) In fact, the intuitions about what happens “at runtime” often differ in Haskell than other languages, not least because GHC performs aggressive optimization.
Idiomatic Haskell is based on parametric polymorphism ; a function of type replicate :: Int -> a -> [a] works exactly the same for any type a . As a result, we know a lot about what replicate does without having to look at its implementation. This relationship is really helpful, and it deeply infects the brain of Haskell programmers. You will notice that I and many other Haskell programmers are crazy about type annotations, especially on a forum like this. Static types are very significant. (Keyword: free theorems.) (This does not apply directly to your question.)
Haskell uses type classes that allow ad hoc polymorphism . In my opinion, "ad hoc" refers to the fact that the implementation of a function can be different for different types. This, of course, is important for numeric types and has been applied for many years in countless ways. But it is important to understand that it is still statically typed, even with class types. To actually evaluate any type-type function - to get a value from it, you need to select a specific type at the end. (With numeric types, default rules often choose it for you.) Of course, you can combine things to create another polymorphic function (or value).
Historically, class classes were considered strictly a mechanism of function overloading in the sense of the same name for several different functions. In other words, instead of addInt :: Int -> Int -> Int , addFloat :: Float -> Float -> Float , we have one name: (+) :: Num a => a -> a -> a . But this is still basically the same idea: there are a bunch of completely different functions called (+) . (Now we tend to talk about class types in terms of "laws", but that's another topic.) Often there is no literal dispatch with a function of type (+) or even non-primitive functions.
Yes, class types are a bit like interfaces, but don't let OOP thinking creep in too much. If you are writing a function with a type of type Num a => a -> a , expect the only thing you know about a is that it is an instance of Num . You cannot look behind the curtain. (Without cheating. It's complicated). The only way to manipulate values of type a is with fully polymorphic functions and other Num functions. In particular, you cannot determine if a also an instance of any other class.
The various compiler extensions that we played blurred this model a bit, because now we can write, in fact, types of level functions. But do not confuse this with dynamic dispatch.
Oh, by the way, Haskell supports dynamic types. See Data.Dymamic . Honestly, I have never seen much use for it beyond interacting with other languages. (I'm ready to make mistakes.) Typical problems with the visitor pattern can be implemented in other ways.