Which polymorphism types are supported in Haskell? - haskell

Reading the Wikipedia definition of polymorphism, I come with a question:
Which polymorphism types are supported in Haskell and which are not?
Looks like Wikipedia do not contain a description for some polymorphism types like Levity Polymorphism which is new for me and supported in Haskell.
I wondering to have an extended list of Haskell Polymorphism followed with examples to explore deeply.
Looks like the main two are:
Parametric polymorphism
Ad-hoc polymorphism

There are at least four things that can count as polymorphism in current Haskell:
Parametric polymorphism. (Also kind polymorphism, polymorphism in the kinds instead of the types. Which I guess is parametric polymorphism one level above, so I'm not counting it as a separate entry.)
Ad-hoc polymorphism, the one enabled by typeclasses. Introduced in the How to make ad-hoc polymorphism less ad hoc paper.
Structural polymorphism. This is the one enabled by generics. A function can work over multiple data types that have different number of fields and constructors. For example, a generic equality function for records.
Levity polymorphism. Polymorphism over calling conventions / runtime representations of types. Described in the Levity Polymorphism paper.
There are two more types of polymorphism that might be introduced in future versions of Haskell:
Matchability polymorphism. Would allow higher-order type families to work with both type constructors and type families as arguments. Described in the paper Higher-order Type-level Programming in Haskell.
Multiplicity polymorphism. Would allow higher-order functions to work with both normal functions and linear functions as arguments. Described in the paper Linear Haskell Practical Linearity in a Higher-Order Polymorphic Language.
One might ask, why this whole panoply of polymorphisms? There seems to exist an overall design principle in Haskell that, whenever some challenge could be solved with either subtyping or polymorphism, polymorphism should be preferred.
For example, from the levity polymorphism paper:
We can now present the main idea of the paper: replace sub-kinding
with kind polymorphism.
From the paper introducing matchability polymorphism:
At first you might think that we need subtyping, but instead we turn to polymorphism
From the linear Haskell paper:
The lack of subtyping is a deliberate choice in our design
Simon Peyton Jones himself makes the point at 47:00 in this talk.
Whenever you want to use subtyping, use polymorphism instead.

Related

What is the difference between traits in Rust and typeclasses in Haskell?

Traits in Rust seem at least superficially similar to typeclasses in Haskell, however I've seen people write that there are some differences between them. I was wondering exactly what these differences are.
At the basic level, there's not much difference, but they're still there.
Haskell describes functions or values defined in a typeclass as 'methods', just as traits describe OOP methods in the objects they enclose. However, Haskell deals with these differently, treating them as individual values rather than pinning them to an object as OOP would lead one to do. This is about the most obvious surface-level difference there is.
The one thing that Rust could not do for a while was higher-order typed traits, such as the infamous Functor and Monad typeclasses.
This means that Rust traits could only describe what's often called a 'concrete type', in other words, one without a generic argument. Haskell from the start could make higher-order typeclasses which use types similar to how higher-order functions use other functions: using one to describe another. For a period of time this was not possible in Rust, but since associated items have been implemented, such traits have become commonplace and idiomatic.
So if we ignore extensions, they are not exactly the same, but each can approximate what the other can do.
It is also mentionable, as said in the comments, that GHC (Haskell's principal compiler) supports further options for typeclasses, including multi-parameter (i.e. many types involved) typeclasses, and functional dependencies, a lovely option that allows for type-level computations, and leads on to type families. To my knowledge, Rust has neither funDeps or type families, though it may in the future.†
All in all, traits and typeclasses have fundamental differences, which due to the way they interact, make them act and seem quite similar in the end.
† A nice article on Haskell's typeclasses (including higher-typed ones) can be found here, and the Rust by Example chapter on traits may be found here
I think the current answers overlook the most fundamental differences between Rust traits and Haskell type classes. These differences have to do with the way traits are related to object oriented language constructs. For information about this, see the Rust book.
A trait declaration creates a trait type. This means that you can declare variables of such a type (or rather, references of the type). You can also use trait types as parameters on function, struct fields and type parameter instantiations.
A trait reference variable can at runtime contain objects of different types, as long as the runtime type of the referenced object implements the trait.
// The shape variable might contain a Square or a Circle,
// we don't know until runtime
let shape: &Shape = get_unknown_shape();
// Might contain different kinds of shapes at the same time
let shapes: Vec<&Shape> = get_shapes();
This is not how type classes work. Type classes create no types, so you can't declare variables with the class name. Type classes act as bounds on type parameters, but the type parameters must be instantiated with a concrete type, not the type class itself.
You can not have a list of different things of different types which implement the same type class. (Instead, existential types are used in Haskell to express a similar thing.) Note 1
Trait methods can be dynamically dispatched. This is strongly related to the things that are described in the section above.
Dynamic dispatch means that the runtime type of the object a reference points is used to determine which method that is called though the reference.
let shape: &Shape = get_unknown_shape();
// This calls a method, which might be Square.area or
// Circle.area depending on the runtime type of shape
print!("Area: {}", shape.area());
Again, existential types are used for this in Haskell.
In Conclusion
It seems to me like traits are in many aspects the same concept as type classes. It addition, they have the functionality of object oriented interfaces.
On the other hand Haskell's type classes are more advanced. Haskell has for example higher-kinded types and extension like multi-parameter type classes.
Note 1: Recent versions of Rust have an update to differentiate the usage of trait names as types and the usage of trait names as bounds. In a trait type the name is prefixed by the dyn keyword. See for example this answer for more information.
Rust's “traits” are analogous to Haskell's type classes.
The main difference with Haskell is that traits only intervene for expressions with dot notation, i.e. of the form a.foo(b).
Haskell type classes extend to higher-order types. Rust traits only don't support higher order types because they are missing from the whole language, i.e. it's not a philosophical difference between traits and type classes
Rust's traits are like Haskell's type classes. They are a way of grouping similar functions together.
The main difference between Haskell and other programming languages is that traits only work with expressions that use dot notation, such as a.foo(b).
In Haskell, type classes can extend to higher-order types. This means that you can create types that behave like other types in the language, by using traits. Rust doesn't have this feature, because it doesn't have higher-order types.

Is there any use for kinds of kinds?

In Haskell, kinds (types of types) allow for some useful things such as type constructors. My question is, would there be any benefit at all to also having kinds of kinds (types of types of types), or is there nothing they could do that couldn't easily be done with just kinds and types?
Ωmega has sorts all the way up. Basically, it is claimed that an infinite kind hierarchy together with appropriate GADTs is as powerful as dependent types.
Also, when trying out stuff using DataKinds, PolyKinds and the like, I sometimes feel constrained somewhat by how type constructors are not lifted to kind constructors, or that lifted kinds cannot be constrained (i.e., there are no kind classes). Ωmega seems to solve a lot of these restrictions -- unfortunately, as it is often the case, at the cost of becoming a more academic language. But I still find it easier to read compared to "real" dependently typed languages like Agda and Coq (although at least Agda does have an infinite hierarchy of sorts, too). Maybe that's because Ωmega just fits more to a Haskell mindset.

Why aren't there many discussions about co- and contra-variance in Haskell (as opposed to Scala or C#)?

I know what covariance and contravariance of types are. My question is why haven't I encountered discussion of these concepts yet in my study of Haskell (as opposed to, say, Scala)?
It seems there is a fundamental difference in the way Haskell views types as opposed to Scala or C#, and I'd like to articulate what that difference is.
Or maybe I'm wrong and I just haven't learned enough Haskell yet :-)
There are two main reasons:
Haskell lacks an inherent notion of subtyping, so in general variance is less relevant.
Contravariance mostly appears where mutability is involved, so most data types in Haskell would simply be covariant and there'd be little value to distinguishing that explicitly.
However, the concepts do apply--for instance, the lifting operation performed by fmap for Functor instances is actually covariant; the terms co-/contravariance are used in Category Theory to talk about functors. The contravariant package defines a type class for contravariant functors, and if you look at the instance list you'll see why I said it's much less common.
There are also places where the idea shows up implicitly, in how manual conversions work--the various numeric type classes define conversions to and from basic types like Integer and Rational, and the module Data.List contains generic versions of some standard functions. If you look at the types of these generic versions you'll see that Integral constraints (giving toInteger) are used on types in contravariant position, while Num constraints (giving fromInteger) are used for covariant position.
There are no "sub-types" in Haskell, so covariance and contravariance don't make any sense.
In Scala, you have e.g. Option[+A] with the subclasses Some[+A] and None. You have to provide the covariance annotations + to say that an Option[Foo] is an Option[Bar] if Foo extends Bar. Because of the presence of sub-types, this is necessary.
In Haskell, there are no sub-types. The equivalent of Option in Haskell, called Maybe, has this definition:
data Maybe a = Nothing | Just a
The type variable a can only ever be one type, so no further information about it is necessary.
As mentioned, Haskell does not have subtypes. However, if you're looking at typeclasses it may not be clear how that works without subtyping.
Typeclasses specify predicates on types, not types themselves. So when a Typeclass has a superclass (e.g. Eq a => Ord a), that doesn't mean instances are subtypes, because only the predicates are inherited, not the types themselves.
Also, co-, contra-, and in- variance mean different things in different fields of math (see Wikipedia). For example the terms covariant and contravariant are used in functors (which in turn are used in Haskell), but the terms mean something completely different. The term invariant can be used in a lot of places.

What is Haskell's Data.Typeable?

I've come across references to Haskell's Data.Typeable, but it's not clear to me why I would want to use it in my code.
What problem does it solve, and how?
Data.Typeable is an encoding of an well known approach (see e.g. Harper) to implementing delayed (dynamic) type checking in a statically typed language -- using a universal type.
Such a type wraps code for which type checking would not succeed until a later phase. Rather than reject the program as ill-typed, the compiler passes it on for runtime checking.
The style originated in Abadi et al., and developed for Haskell by Cheney and Hinze as a wrapper to represent all dynamic types, with the Typeable class appearing as part of the SYB work of SPJ and Lammel.
Reference
Martín Abadi, Luca Cardelli, Benjamin Pierce and Gordon Plotkin, "Dynamic Typing in a Statically Typed Language", ACM Transactions on Programming Languages and Systems (TOPLAS), 1991.
James Cheney and Ralf Hinze, "A lightweight implementation of generics and dynamics", Haskell '02: Proceedings of the 2002 ACM SIGPLAN Workshop on Haskell, 2002.
Lammel, Ralf and Jones, Simon Peyton, "Scrap your boilerplate: a practical design pattern for generic programming, TLDI '03: Proceedings of the 2003 ACM SIGPLAN International Workshop on Types in Languages Design and Implementation, 2003
Harper, 2011, Practical Foundations for Programming Languages.
Even in the text books: dynamic types (with typeable representations) are statically typed languages with only one type, Harper ch 20:
20.4 Untyped Means Uni-Typed
The untyped λ-calculus may be faithfully embedded in a
typed language with recursive types. This means that every
untyped λ-term has a representation as a typed expression
in such a way that execution of the representation of a
λ-term corresponds to execution of the term itself. This
embedding is not a matter of writing an interpreter for
the λ-calculus in ℒ{+×⇀µ} (which we could surely do), but
rather a direct representation of untyped λ-terms as typed
expressions in a language with recursive types.
The key observation is that the untyped λ-calculus is
really the uni-typed λ-calculus! It is not the absence
of types that gives it its power, but rather that it has
only one type, namely the recursive type
D = µt.t → t.
It's a library that allows, among other things, naming types. If a type a is declared Typeable, then you can get its name using show $ typeOf x where x is any value of type a. It also features limited type-casting.
(This is somewhat similar to C++'s RTTI or dynamic languages' reflection.)
One of the earliest descriptions I could find of a Data.Typeable-like library for Haskell is by John Peterson from 1992: http://www.cs.yale.edu/publications/techreports/tr1022.pdf
The earliest "official" paper I know of introducing the actual Data.Typeable library is the first Scrap Your Boilerplate paper from 2003: http://research.microsoft.com/en-us/um/people/simonpj/Papers/hmap/index.htm
I'm sure there's lots of intervening history that someone here can chime in with!
The Data.Typeable class is used primarily for generic programming in the Scrap Your Boilerplate (SYB) style. See also Data.Data
The idea is that SYB defines a collection combinators for performing operations such as printing, counting, searching, substiting, etc in a uniform manner over a variety of user-created types. The Typeable typeclass provides the necessary plumbing.
In modern GHC, you can just say deriving Data.Typeable when defining your own type in order to provide it with the necessary instances.

What makes Haskell's type system more "powerful" than other languages' type systems?

Reading Disadvantages of Scala type system versus Haskell?, I have to ask: what is it, specifically, that makes Haskell's type system more powerful than other languages' type systems (C, C++, Java). Apparently, even Scala can't perform some of the same powers as Haskell's type system. What is it, specifically, that makes Haskell's type system (Hindley–Milner type inference) so powerful? Can you give an example?
What is it, specifically, that makes Haskell's type system
It has been engineered for the past decade to be both flexible -- as a logic for property verification -- and powerful.
Haskell's type system has been developed over the years to encourage a relatively flexible, expressive static checking discipline, with several groups of researchers identifying type system techniques that enable powerful new classes of compile-time verification. Scala's is relatively undeveloped in that area.
That is, Haskell/GHC provides a logic that is both powerful and designed to encourage type level programming. Something fairly unique in the world of functional programming.
Some papers that give a flavor of the direction the engineering effort on Haskell's type system has taken:
Fun with type functions
Associated types with class
Fun with functional dependencies
Hindley-Milner is not a type system, but a type inference algorithm. Haskell's type system, back in the day, used to be able to be fully inferred using HM, but that ship has long sailed for modern Haskell with extensions. (ML remains capable of being fully inferred).
Arguably, the ability to mainly or entirely infer all types yields power in terms of expressiveness.
But that's largely not what I think the question is really about.
The papers that dons linked point to the other aspect -- that the extensions to Haskell's type system make it turing complete (and that modern type families make that turing complete language much more closely resemble value-level programming). Another nice paper on this topic is McBride's Faking It: Simulating Dependent Types in Haskell.
The paper in the other thread on Scala: "Type Classes as Objects and Implicits" goes into why you can in fact do most of this in Scala as well, although with a bit more explicitness. I tend to feel, but this is more a gut sense than from real Scala experience, that its more ad-hoc and explicit approach (what the C++ discussion called "nominal") is ultimately a bit messier.
Let's go with a very simple example: Haskell's Maybe.
data Maybe a = Nothing | Just a
In C++:
template <T>
struct Maybe {
bool isJust;
T value; // IMPORTANT: must ignore when !isJust
};
Let's consider these two function signatures, in Haskell:
sumJusts :: Num a => [Maybe a] -> a
and C++:
template <T> T sumJusts(vector<maybe<T> >);
Differences:
In C++ there are more possible mistakes to make. The compiler doesn't check the usage rule of Maybe.
The C++ type of sumJusts does not specify that it requires + and cast from 0. The error messages that show up when things do not work are cryptic and odd. In Haskell the compiler will just complain that the type is not an instance of Num, very straightforward..
In short, Haskell has:
ADTs
Type-classes
A very friendly syntax and good support for generics (which in C++ people try to avoid because of all their cryptickynessishisms)
Haskell language allows you to write safer code without giving up with functionalities. Most languages nowadays trade features for safety: the Haskell language is there to show that's possible to have both.
We can live without null pointers, explicit castings, loose typing and still have a perfectly expressive language, able to produce efficient final code.
More, the Haskell type system, along with its lazy-by-default and purity approach to coding gives you a boost in complicate but important matters like parallelism and concurrency.
Just my two cents.
One thing I really like and miss in other languages is the support of typclasses, which are an elegant solution for many problems (including for instance polyvariadic functions).
Using typeclasses, it's extremely easy to define very abstract functions, which are still completely type-safe - like for instance this Fibonacci-function:
fibs :: Num a => [a]
fibs#(_:xs) = 0:1:zipWith (+) fibs xs
For instance:
map (`div` 2) fibs -- integral context
(fibs !! 10) + 1.234 -- rational context
map (:+ 1.0) fibs -- Complex context
You may even define your own numeric type for this.
What is expressiveness? To my understanding it is what constraint the type system allow us to put on our code, or in other words what properties of code which we can prove. The more expressive a type system is, the more information we can embed at the type level (which can be used at compile time by the type-checker to check our code).
Here are some properties of Haskell's type system that other languages don't have.
Purity.
Purity allows Haskell to distinguish pure code and IO capable code
Paramtricity.
Haskell enforces parametricity for parametrically polymorphic functions so they must obey some laws. (Some languages does let you to express polymorphic function types but they don't enforce parametricity, for example Scala lets you to pattern match on a specific type even if the argument is polymorphic)
ADT
Extensions
Haskell's base type system is a weaker version of λ2 which itself isn't really impressive. But with these extensions it become really powerful (even able to express dependent types with singleton):
existential types
rank-n types (full λ2)
type families
data kinds (allows "typed" programming at type level)
GADT
...

Resources