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

11,893

Solution 1

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:

Solution 2

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.

Solution 3

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)

Solution 4

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.

Solution 5

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.

Share:
11,893
Admin
Author by

Admin

Updated on June 24, 2022

Comments

  • Admin
    Admin almost 2 years

    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?

  • Daenyth
    Daenyth over 13 years
    Fibonacci isn't a great example for this since it's only defined for integers afaik
  • Jochen Ritzel
    Jochen Ritzel over 13 years
    Yeah, I think Functor would be a great example.
  • fuz
    fuz over 13 years
    Was just a silly example for how easy and natural type-overloading is in haskell.
  • u0b34a0f6ae
    u0b34a0f6ae over 12 years
    This also illustrates "overloading" based on "return type".
  • Trismegistos
    Trismegistos almost 11 years
    You should implement accessors and they would throw proper exceptions.
  • yairchu
    yairchu almost 11 years
    @Trismegistos: Exceptions would turn make mistakes into runtime errors, rather than compile time errors in Haskell.
  • YellPika
    YellPika about 10 years
    @yairchu You can still write let Just x = Nothing in ... in Haskell. You won't even get a warning (without a compiler flag). The real advantages of Haskell's type system lie elsewhere.
  • yairchu
    yairchu about 10 years
    @YellPika: In your example you would get a run-time error in Haskell rather than strange undefined behavior masking the bug which you get in C++..
  • YellPika
    YellPika about 10 years
    @yairchu: I was referring to the hypothetical guard-your-fields-with-accessors version. In both cases, the type system doesn't statically prevent errors (where "strange undefined behaviour" counts as an error).
  • yairchu
    yairchu about 10 years
    @YellPika: Good point. So with C++ best practices you can get on par to what you get in Haskell for free. But note that if you employ best practices in Haskell you (using pattern matching and higher order functions like fmap) you'll avoid the possibility of this run-time error.
  • josiah
    josiah almost 10 years
    It would be nice to have Haskell on the JVM to replace Scala, but past attempts have shown that this is either not practical or very difficult (I'm thinking of Jaskell in particular). Some things about the JVM annoy me, like the inability to do tail-call optimization (which Clojure hacked around by providing the recur function). Ultimately, I think the best approach would be to get the industry focusing more on developing the Erlang VM.
  • Dierk
    Dierk over 9 years
    If you search for a Haskell on the JVM, your currently best option is Frege.
  • user982333
    user982333 about 9 years
    Actually, implementation of Maybe in C++ is a lot more complicated than this. Here's the real thing: boost.org/doc/libs/1_58_0/boost/optional/optional.hpp. Shear amount of code suggests that C++ type system has some serious weaknesses.
  • Tim Harper
    Tim Harper over 8 years
    Scala does tail-call optimization.
  • sclv
    sclv over 8 years
    This is neither here nor there, but scala can opportunistically convert a single function calling itself in tail position into a "trampolined" version. This is less efficient than tail call optimization, and if you have two or more mutually recursive functions calling one another in tail position (a not uncommon occurrence) then it is of no help at all, and you need to rewrite the entire system into a single function with some form of "state" flag.
  • Antimony
    Antimony about 8 years
    If you really want to, you can statically check all those things in C++. It's just harder and more verbose.