8

Why are these not equivalent?

show $ if someCondition then someInt else some double

and

if someCondition then show someInt else show someDouble

I understand that if you isolate the if ... else part in the first example to an expression by itself then you can't represent its type with an anonymous sum type, the kind of Int | Double, like something you could do easily in TypeScript (mentioning TypeScript because it is the langauge I used often and that supports Sum types), and would have to resort to using the Either data then based on it would call show.

The example I gave here is trivial but to me it makes more sense to think "Okay we are going to show something, and that something depends on someCondition" rather than "Okay if someCondition is true then show someInt otherwise show someDouble", and also allows for less code duplication (here the show is repeated twice but it could also be a long function application and instead of an if ... else there could be >2 branches to consider)

In my mind it should be easy for the compiler to check if each of the types that make the sum type (here Int | Double) could be used as a parameter to show function and decides if the types are correct or not. Even better is that show function always returns a string no matter the types of the parameters, so the compiler doesn't have to carry with it all the possible "branches" (so all the possible types).

Is it by choice that such a feature doesn't exist? Or is implementing it harder that I think?

Mehdi Saffar
  • 691
  • 1
  • 8
  • 24
  • 2
    An `if ... then ... else ...`, needs to have the same type in the `then` and `else` part. You can see it as a ternary operator in some programming languages. – Willem Van Onsem Nov 09 '19 at 20:01
  • To the best of my knowledge, there is no *anonymous* sum type, as in, all conversions are done explicitly. You can use `Left` and `Right` to convert it to an `Either Int Double`. I don't see why implementing it would be that hard. But Haskell aims to make all "conversions" explicit. So even converting an `Int` to a `Double`, something that a lot of languages do implicitly, is done explicitly. – Willem Van Onsem Nov 09 '19 at 20:06
  • 1
    I agree about the `making all conversions explicit`. In my question, I do not want Haskell cast an `Int` to a `Double` or vice versa. I just used those two types as an example. You could replace every `Int` with `a` and `Double` with `b` in my question where both types derive `Show`. I understand that there are no `anonymous sum types` in Haskell but I would like to know why that is the case and what is stopping us from designing the language to have it. – Mehdi Saffar Nov 09 '19 at 20:10
  • 4
    I think such types are called _union types_. A sum type is essentially a _tagged_ variant of the union type, where each value must carry a left / right tag beyond the value of the inner type. I expect that type inference with union types, with all the type-level features of Haskell is very hard to achieve. If `x :: Int | Bool` and we have to compile `show x`, there's no easy way to know which pointer-to-function to use for calling `show`, in a type-erasure based RTS. We'd probably need to keep some type level information at runtime. – chi Nov 09 '19 at 20:19
  • 1
    Not having anonymous sum types is a design decision of Haskell designers. I'm not quite sure what kind of benefit they would bring to the table, bit I do see where they would complicate things. So my guess is that they are left out because the cost/benefit ratio isn't there. But to be absolutely sure you need to ask the original language designers and/or current maintainers. I don't think it would make a great SO question, because there's a great deal of personal opinions and tastes involved in designing a language. – n. m. could be an AI Nov 09 '19 at 20:25
  • @chi So if I understand correctly, for the compiler to *typecheck* that both `Int` and `Double` could be passed to `show` is easy, but the actual compilation step would require having some runtime type information to be able to decide what "variant" of the `show` function to call based on which of `Int` or `Bool` it happens to take when the `if ... else ...` is evaluated, correct? – Mehdi Saffar Nov 09 '19 at 20:26
  • @n.'pronouns'm. the way I think of it is since we have tuples like `(String, Int)` which are essentially anonymous product types, we should also have `(String | Int)`, union types, which are essentially anonymous sum types. – Mehdi Saffar Nov 09 '19 at 20:29
  • @chi The question as I see it is not why Haskell doesn't have un-discriminated unions. These are clearly out of the question. The question is why sum types need to be explicitly constructed with a relatively heavyweight syntax. – n. m. could be an AI Nov 09 '19 at 20:31
  • 4
    `(String, Int)` is not anonymous. It is just a regular product type with funny syntax. `(String | Int)` would be a whole lot different. Start with asking yourself whether `(Int|Int)` should be identical to `Int` and why. – n. m. could be an AI Nov 09 '19 at 20:33
  • I would say `(Int|Int)` is identical to `Int` except that it is redundant. Out of curiosity, in TypeScript I defined `const a : Number | Number = 2` and it didn't complain. – Mehdi Saffar Nov 09 '19 at 20:38
  • Indeed, TypeScript does support union and intersection types, even though Haskell doesn't. – Joseph Sible-Reinstate Monica Nov 09 '19 at 20:40

2 Answers2

8

All parts of an expression must be well-typed. The type of if someCondition then someInt else someDouble would have to be something like exists a. Show a => a, but Haskell doesn't support that kind of existential quantification.

Update: As chi points out in a comment, this would also be possible if Haskell had support for union/intersection types (which are not the same as sum/product types), but it unfortunately doesn't.

  • Could you please elaborate on the difference between union/intersection types and sum/product types? I have always thought they were the same, except for anonymity? – Mehdi Saffar Nov 09 '19 at 20:30
  • 1
    @MehdiSaffar Anonymous as in untagged, not as in an unnamed constructor. In other words, if you have an `Int ∪ Double`, then you'd know you have one of the two, but wouldn't be able to pattern match to see which, so you could only do things that would be valid to do for both possibilities. – Joseph Sible-Reinstate Monica Nov 09 '19 at 20:38
  • 2
    For clarity, TypeScript has type information available at runtime, so it has a `typeof` operator that can make up for the lack of tagging and see which type is used anyway. Haskell is fully type-erased, so if it supported this feature, then there wouldn't be any equivalent to that. – Joseph Sible-Reinstate Monica Nov 09 '19 at 20:42
7

There are product types with lightweight syntax, written (,), in Haskell. One would thing that a sum type with a lightweight syntax, something like (Int | String), would be a great idea. The reality is more complicated. Let's see why (I'm taking some liberties with Num, they are not important).

if someCondition then 42 else "helloWorld"

If this should return a value of type like (Int | String), then what should the following return?

if someCondition then 42 else 0

(Int | Int) obviously, but if this is distinct from plain Int then we're in deep trouble. So (Int | Int) should be identical to plain Int.

One can immediately see that this is not just lightweight syntax for sum types, but a wholly new language feature. A different kind of type system if you will. Should we have one?

Let's look at this function.

mysteryType x a b = if x then a else b

Now what type does mysteryType have? Obviously

mysteryType :: Bool -> a -> b -> (a|b)

right? Now what if a and b are the same type?

let x = mysteryType True 42 0

This should be plain Int as we have agreed previously. Now mysteryType sometimes return an anonymous sum type, and sometimes it does not, depending on what arguments you pass. How would you pattern match such an expression? What on Earth can you do with it? Except trivial things like "show" (or whatever methods of other type-classes it would be an instance of), not a whole lot. Unless you add run-time type information to the language, that is, so typeof is available — and that make Haskell an entirely different language.

So yeah. Why isn't Haskell a TypeScript? Because we don't need another TypeScript. If you want TypeScript, you know where to find it.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243