Why do We Need Sum Types?

前端 未结 4 628
[愿得一人]
[愿得一人] 2021-02-08 04:56

Imagine a language which doesn\'t allow multiple value constructors for a data type. Instead of writing

data Color = White | Black | Blue

we wo

4条回答
  •  执念已碎
    2021-02-08 05:53

    Given that you mention TypeScript, it is instructive to have a look at what its docs have to say about its union types. The example there starts from a function...

    function padLeft(value: string, padding: any) { //etc.
    

    ... that has a flaw:

    The problem with padLeft is that its padding parameter is typed as any. That means that we can call it with an argument that’s neither a number nor a string

    One plausible solution is then suggested, and rejected:

    In traditional object-oriented code, we might abstract over the two types by creating a hierarchy of types. While this is much more explicit, it’s also a little bit overkill.

    Rather, the handbook suggests...

    Instead of any, we can use a union type for the padding parameter:

    function padLeft(value: string, padding: string | number) { // etc.
    

    Crucially, the concept of union type is then described in this way:

    A union type describes a value that can be one of several types.

    A string | number value in TypeScript can be either of string type or of number type, as string and number are subtypes of string | number (cf. Alexis King's comment to the question). An Either String Int value in Haskell, however, is neither of String type nor of Int type -- its only, monomorphic, type is Either String Int. Further implications of that difference show up in the remainder of the discussion:

    If we have a value that has a union type, we can only access members that are common to all types in the union.

    In a roughly analogous Haskell scenario, if we have, say, an Either Double Int, we cannot apply (2*) directly on it, even though both Double and Int have instances of Num. Rather, something like bimap is necessary.

    What happens when we need to know specifically whether we have a Fish? [...] we’ll need to use a type assertion:

    let pet = getSmallPet();
    
    if ((pet).swim) {
        (pet).swim();
    }
    else {
        (pet).fly();
    }
    

    This sort of downcasting/runtime type checking is at odds with how the Haskell type system ordinarily works, even though it can be implemented using the very same type system (also cf. leftaroundabout's answer). In contrast, there is nothing to figure out at runtime about the type of an Either Fish Bird: the case analysis happens at value level, and there is no need to deal with anything failing and producing Nothing (or worse, null) due to runtime type mismatches.

提交回复
热议问题