Visitor Pattern
The object-oriented incarnation of pattern matching is the visitor pattern. I've used "match", instead of "visit" in the following snippet to emphasize the correspondence.
// OCaml: `let action1 = VisitPage "www.myweb.com/help"`
const action1 = {
match: function (matcher) {
matcher.visitPage('www.myweb.com/help');
}
};
// OCaml: `let action2 = DeletePost 12345`
const action2 = {
match: function (matcher) {
matcher.deletePost(12345);
}
};
// OCaml: `let action2 = ViewUser SoftTimur`
const action3 = {
match: function (matcher) {
matcher.viewUser('SoftTimur');
}
};
// These correspond to a `match ... with` construct in OCaml.
const consoleMatcher = {
visitPage: function (url) {
console.log(url);
},
deletePost: function (id) {
console.log(id);
},
viewUser: function (username) {
console.log(username);
}
};
action1.match(consoleMatcher);
action2.match(consoleMatcher);
action3.match(consoleMatcher);
After some refactoring, you can obtain something like this, which looks pretty close to what OCaml offers:
function Variant(name) {
return function (...args) {
return { match(matcher) { return matcher[name](...args); } };
};
}
const Action = {
VisitPage: Variant('VisitPage'),
DeletePost: Variant('DeletePost'),
ViewUser: Variant('ViewUser'),
};
const action1 = Action.VisitPage('www.myweb.com/help');
const action2 = Action.DeletePost(12345);
const action3 = Action.ViewUser('SoftTimur');
const consoleMatcher = {
VisitPage(url) { console.log(url) },
DeletePost(id) { console.log(id) },
ViewUser(username) { console.log(username) },
};
action1.match(consoleMatcher);
action2.match(consoleMatcher);
action3.match(consoleMatcher);
Or
action1.match({
VisitPage(url) { console.log(url) },
DeletePost(id) { console.log(id) },
ViewUser(username) { console.log(username) },
});
Or even (using ES2015 anonymous classes):
action1.match(class {
static VisitPage(url) { console.log(url) }
static DeletePost(id) { console.log(id) }
static ViewUser(username) { console.log(username) }
});
The advantage over OCaml is that the match block is first class, just like functions. You can store it in variables, pass it to functions and return it from functions.
To eliminate the code duplication in variant names, we can devise a helper:
function Variants(...names) {
const variant = (name) => (...args) => ({
match(matcher) { return matcher[name](...args) }
});
const variants = names.map(name => ({ [name]: variant(name) }));
return Object.assign({}, ...variants);
}
const Action = Variants('VisitPage', 'DeletePost', 'ViewUser');
const action1 = Action.VisitPage('www.myweb.com/help');
action1.match({
VisitPage(url) { console.log(url) },
DeletePost(id) { console.log(id) },
ViewUser(username) { console.log(username) },
});