4
votes

I have only a theoretical idea of Haskell's sum types. And yet I sense that they really matter in Haskell and change the way you model your data in a fundamental way. Since I believe that they can be useful in dynamically typed languages as well, I tried to implement a sensible approximation in Javascript (I have only a superficial knowledge of Haskell).

Here is a more or less useful example of a Name sum type, that is able to handle various name formats. I know that Haskell distinguishes between type and data constructors and probably has good reasons to make such distinction. However, I guess that it is not possible to map this concept to Javascript. And pattern matching neither.

Anyway, my actual question is: Does the following implementation embody the nature of sum types and the way they are applied in Haskell?

Please note: I am not sure if this kind of cross-language questions are welcome on SO. Please let me know in case I should avoid them.

// auxiliary functions

const A = f => x => f(x);
const show = api => api.show;

// the type constructor

const Name = (...xs) => A(({length: len}) => {
  switch (len) {
    case 1: {
      let [{length: len}] = xs; // no pattern matching but destructuring

      if (len > 1) { // no Char type in JS
        return k => k({show: () => xs[0]}); // returns the API
      }

      break;
    }

    case 2: {
      let [{length: len}, {length: len2}] = xs;

      if (len > 1 && len2 > 1) {
        return k => k({show: () => `${xs[0]} ${xs[1]}`});
      }

      if (len === 1 && len2 > 1) {
        return k => k({show: () => `${xs[0]}. ${xs[1]}`});
      }

      break;
    }

    case 3: {
      let [{length: len}, {length: len2}, {length: len3}] = xs;

      if (len > 1 && len2 > 1 && len3 > 1) {
        return k => k({show: () => `${xs[0]} ${xs[1]} ${xs[2]}`});
      }

      if (len > 1 && len2 === 1 && len3 > 1) {
        return k => k({show: () => `${xs[0]} ${xs[1]}. ${xs[2]}`});
      }

      if (len === 1 && len2 === 1 && len3 > 1) {
        return k => k({show: () => `${xs[0]}. ${xs[1]}. ${xs[2]}`});
      }
    }

    default: throw new TypeError();
  }
}) (xs);

// run

console.log(Name("Kerouac") (show) ());
console.log(Name("Hans", "Hölzel") (show) ());
console.log(Name("H", "Curry") (show) ());
console.log(Name("Jean", "Luc", "Godard") (show) ());
console.log(Name("William", "S", "Burroughs") (show) ());
console.log(Name("E", "W", "Dijkstra") (show) ());

[EDIT]

Sorry, I should have provided some Haskell code:

type FirstName = String
type LastName = String
type MiddleName = String

data Name = FullName FirstName LastName
  | NameWithMiddle FirstName MiddleName LastName
  | NameWithMiddleInitial FirstName Char LastName
  | TwoInitialsWithLast Char Char LastName
  | OneInitialWithLast Char LastName
  | LastNameOnly LastName

I am not sure if this is valid though.

I guess the problem with my approach is that I try to implement the type constructor Name, while I should implement the value constructors, right?

1
Could you please show the definition of the Haskell type you're trying to imitate?4castle
I have no idea about how the code above would be related to a sum type. A sum type has two constructors (or N>=2 constructors, in its N-ary variant), and one destructor/eliminator with 2 (or N) arguments. You might wish to look up the Church encoding of sum types.chi
This implementation of a 'sum type' seems to do two things which differentiate it from actual sum types; firstly, it is the CPS encoding, but not of the sum, rather of the product in each variant of the sum (the 'sum' is the switch); and what is typically called a 'smart constructor' is embedded in the type definition itself. You also have a 'type' which is essentially Either Char String, but this is much easier to write as (Bool, String) especially in JS. While I see how this is 'technically' a sum type, it's not used like this in Haskell (esp. embedding smart constructors in the type).user2407038
Yes, mixing up types with constructors is a very bad idea. You should implement the same distinction in your JS code.Bergi
@ftor Something like daggy, perhaps? It uses a kind of Church encoding, with some syntactic niceties.phipsgabler

1 Answers

5
votes

I think your encoding is a bit too complex because it involves not just the data type itself, but also the show operation.

The essence of sum types is that they give you a way to create values that represent one or more cases and then write processing code using pattern matching. A slightly simpler version of your example with just full name or a short name would be:

data Name = 
    FullName String String
  | NickName String

Then you can write a function to process a name by handling the two cases separately:

case name of
  FullName first last -> "Full name: " ++ first ++ " " ++ last
  NickName nick -> "Nick name: " ++ nick

The key idea is that when you have a name, you can detect which of the two cases it is and write code to handle the two cases separately. In JavaScript, you can do this by including some sort of tag in your value:

function FullName(first, last) { 
  return { Tag: "FullName", Values: [first, last] };
}
function NickName(nick) { 
  return { "Tag": "NickName", Values: [nick] };
}

Now you can write code akin to pattern matching using switch on the Tag:

switch(name.Tag) { 
  case "FullName": 
    let [first, last] = name.Values;
    return "Full name: " + first + " " + last;
  case "NickName":
    let [nick] = name.Values
    "Nick name: " + nick;
}

However, without the language support, you lose many of the nice features:

  • There are no checks when extracting values. If you modify the type and add more fields, then the pattern matching will start failing.
  • There are no checks whether you are covering all cases - when you add another kind of name, the switch statements will not cover that
  • There is no checking on tag names. It is easy to make a typo.

So, you can certainly use this kind of encoding in JavaScript, but it does not give you as much as in languages that support sum types directly. If there is a more idiomatic JavaScript solution to the problem you are having, it will probably work better.