如何在没有模式匹配的动态类型语言中实现 sum 类型

4 javascript haskell functional-programming pattern-matching gadt

我对 Haskell 的求和类型只有一个理论概念。然而我感觉到它们在 Haskell 中真的很重要,并且从根本上改变了你对数据建模的方式。因为我相信它们在动态类型语言中也很有用,所以我尝试在 Javascript 中实现一个合理的近似(我只对 Haskell 有肤浅的了解)。

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

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

请注意:我不确定 SO 上是否欢迎此类跨语言问题。如果我应该避免它们,请告诉我。

// 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) ());
Run Code Online (Sandbox Code Playgroud)

[编辑]

抱歉,我应该提供一些 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
Run Code Online (Sandbox Code Playgroud)

我不确定这是否有效。

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

Tom*_*cek 5

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

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

data Name = 
    FullName String String
  | NickName String
Run Code Online (Sandbox Code Playgroud)

然后你可以写一个函数来处理一个名字,分别处理这两种情况:

case name of
  FullName first last -> "Full name: " ++ first ++ " " ++ last
  NickName nick -> "Nick name: " ++ nick
Run Code Online (Sandbox Code Playgroud)

关键思想是,当你有了一个名字时,你可以检测它是两种情况中的哪一种,并编写代码来分别处理这两种情况。在 JavaScript 中,你可以通过在你的值中包含某种标签来做到这一点:

function FullName(first, last) { 
  return { Tag: "FullName", Values: [first, last] };
}
function NickName(nick) { 
  return { "Tag": "NickName", Values: [nick] };
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以编写代码类似于使用模式匹配switchTag

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

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

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

因此,您当然可以在 JavaScript 中使用这种编码,但它不会像在直接支持 sum 类型的语言中那样为您提供。如果您遇到的问题有更惯用的 JavaScript 解决方案,它可能会更好地工作。

  • @4castle不,“修改类型”指的是编辑源代码以更改其中一种情况下的字段数量(例如,意识到我们需要在全名情况下为该人的头衔添加一个额外的字段),与运行时值的可变性。 (2认同)