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?
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:
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.