如何在没有模式匹配的情况下在动态类型语言中实现和类型

时间:2017-03-05 21:51:29

标签: javascript haskell functional-programming pattern-matching gadt

我只有Haskell总和类型的理论概念。然而,我感觉它们在Haskell中真的很重要,并且改变了以基本方式建模数据的方式。由于我相信它们在动态类型语言中也很有用,我试图在Javascript中实现一个合理的近似(我只有Haskell的肤浅知识)。

这是一个或多或少有用的Name和类型的例子,它能够处理各种名称格式。我知道Haskell区分了类型和数据构造函数,并且可能有充分的理由进行这种区分。但是,我想这是不可能将这个概念映射到Javascript。并且模式匹配。

无论如何,我的实际问题是:以下实现是否体现了和类型的性质以及它们在Haskell中的应用方式?

请注意:我不确定是否欢迎这种跨语言问题。如果我应该避免,请告诉我。



// 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) ());




[编辑]

抱歉,我应该提供一些Haskell代码:

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

我不确定这是否有效。

我想我的方法的问题是我尝试实现类型构造函数Name,而我应该实现值构造函数,对吗?

1 个答案:

答案 0 :(得分:3)

我认为您的编码有点过于复杂,因为它不仅涉及数据类型本身,还涉及show操作。

sum类型的本质是它们为您提供了一种方法来创建表示一个或多个案例的值,然后使用模式匹配编写处理代码。只需全名或短名称的示例稍微简单一点就是:

data Name = 
    FullName String String
  | NickName String

然后你可以通过分别处理两个案例来编写一个处理名称的函数:

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

关键的想法是,当你有一个名字时,你可以检测它是哪两种情况,并编写代码来分别处理这两种情况。在JavaScript中,您可以通过在值中包含某种标记来实现此目的:

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

现在,您可以使用switch上的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;
}

但是,如果没有语言支持,您将失去许多不错的功能:

  • 提取值时没有检查。如果修改类型并添加更多字段,则模式匹配将开始失败。
  • 没有检查是否涵盖所有情况 - 当您添加其他类型的名称时,switch语句不会涵盖
  • 没有检查标签名称。打个字很容易。

因此,你当然可以在JavaScript中使用这种编码,但它并没有像直接支持和类型的语言那样多。如果对于您遇到的问题有一个更惯用的JavaScript解决方案,它可能会更好。