3 ms·
The types of polymorphism are different. Haskell's parametric polymorphism is compile-time polymorphism, Go's interfaces are run-time polymorphism. If we draw
by danieldk 14y ago
The types of polymorphism are different. Haskell's parametric polymorphism is compile-time polymorphism, Go's interfaces are run-time polymorphism.
If we draw a parallel to C++, Haskell's polymorphism is like templates, Go's interfaces are like abstract base classes (except that they use compile-time duck typing).
For example, consider the (+) function in Haskell:
(+) :: Num n -> n -> n -> n
If you use an Integer as the first argument, the second argument must also be an integer, as well as the return type. You can observe this by currying, binding only the first argument:
Prelude> :type (+) (1 :: Integer)
(+) (1 :: Integer) :: Integer -> Integer
Prelude> :type (+) (1 :: Double)
(+) (1 :: Double) :: Double -> Double
In Go, you could write an interface Num and define a function:
func foo(a Num, b Num) Num
However, if float64 and int both implement the Num interface, you could also foo a float64 and int. And it would return something that conforms to the Num interface, but what type is actually constructed depends on the implementation of foo.
In other words, type classes have relatively little to do with Go interfaces, aside that they both implement a (different) form of polymorphism.
It's possible to make something akin to Go's polymorphism in Haskell, but you'd need to hide the type parameter of the typeclass using existential quantification.
data NumBox = forall n. Num n => MkNumBox n
You can now use the NumBox for runtime polymorphism:
Prelude> :type [1::Int, 4.4::Double]
[...]
Couldn't match expected type `Int' with actual type `Double'
[...]
Prelude> :type [MkNumBox (32 :: Int), MkNumBox (22.32 :: Double)]
[MkNumBox (32 :: Int), MkNumBox (22.32 :: Double)] :: [NumBox]
- kevinnk 14y ago> If we draw a parallel to C++, Haskell's polymorphism is like templates It's important to note that even if Haskell's classes feel like C++ templates they are actually implemented more like abstract classes. In C++ you essentially create a new function for each template instantiation. In Haskell the function is passed an additional pointer telling the function how to implement the class. Kind of like a vtable pointer except not bound to the actual object. This has performance implications, so it's an important point to remember when comparing the languages.
- DanWaterworth 14y agoThat's a good point, however, the important aspect of typeclasses is that they're type-checked, not that they're compiled via monomorphization. Also, I believe that the way that typeclasses are compiled is at the discretion of the compiler, so actually typeclasses may be compiled in the same way as C++ templates. Another point is that monomorphisation isn't always possible, because a function could be run with an infinite number of types.
- fix_error 14y agoGHC is pretty good at specializing type-class-using functions for particular instances, e.g. if you have fac :: (Num a, Eq a) => a -> a fac 0 = 1 fac n = n*fac (n-1) main = do x <- readLn :: IO Int print (fac x) it will create a specialization (without any indirect calls) fac_Int :: Int -> Int and call that (in fact, in this case fac_Int will even be implemented by a worker function of type Int# -> Int# (unboxed ints)). If you don't want to rely on this automatic optimization, you can always add a pragma {-# SPECIALIZE fac :: Int -> Int #-}. (I used a recursive example because a non-recursive function would usually simply be inlined, avoiding any indirect calls too, assuming the call site is monomorphic).