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

Javascript 如何在没有模式匹配的情况下在动态类型语言中实现和类型,javascript,haskell,functional-programming,pattern-matching,gadt,Javascript,Haskell,Functional Programming,Pattern Matching,Gadt,我对Haskell的求和类型只有一个理论上的概念。但我感觉到它们在Haskell中确实很重要,并从根本上改变了数据建模的方式。因为我相信它们在动态类型语言中也很有用,所以我尝试在Javascript中实现一个合理的近似值(我对Haskell只有一点肤浅的了解) 下面是一个或多或少有用的Namesum类型示例,它能够处理各种名称格式。我知道Haskell区分类型构造函数和数据构造函数,并且可能有很好的理由进行这种区分。然而,我想不可能将这个概念映射到Javascript。和模式匹配都不是 无论如何

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

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

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

请注意:我不确定这样的跨语言问题是否受欢迎。请让我知道,以防我避开它们

//辅助函数
常数A=f=>x=>f(x);
const show=api=>api.show;
//类型构造函数
常量名称=(…xs)=>A({length:len})=>{
开关(透镜){
案例1:{
let[{length:len}]=xs;//不进行模式匹配,只进行解构
如果(len>1){//JS中没有字符类型
返回k=>k({show:()=>xs[0]});//返回API
}
打破
}
案例2:{
设[{length:len},{length:len2}]=xs;
如果(len>1&&len2>1){
返回k=>k({show:()=>`${xs[0]}${xs[1]}});
}
如果(len===1&&len2>1){
返回k=>k({show:()=>`${xs[0]}.${xs[1]}`});
}
打破
}
案例3:{
设[{length:len},{length:len2},{length:len3}]=xs;
如果(len>1&&len2>1&&len3>1){
返回k=>k({show:()=>`${xs[0]}${xs[1]}${xs[2]}});
}
如果(len>1&&len2==1&&len3>1){
返回k=>k({show:()=>`${xs[0]}${xs[1]}.${xs[2]}`});
}
如果(len==1&&len2===1&&len3>1){
返回k=>k({show:()=>`${xs[0]}.${xs[1]}.${xs[2]}`});
}
}
默认值:抛出新类型错误();
}
})(xs);
//跑
控制台日志(名称(“Kerouac”)(show)();
console.log(名字(“Hans”,“Hölzel”)(show)();
console.log(名称(“H”,“Curry”)(show)();
控制台日志(名称(“Jean”、“Luc”、“Godard”)(show)();
控制台日志(名称(“威廉”、“S”、“巴勒斯”)(show)();

console.log(名称(“E”、“W”、“Dijkstra”)(show)()我认为您的编码有点太复杂了,因为它不仅涉及数据类型本身,还涉及
show
操作

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

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(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解决方案来解决您遇到的问题,它可能会工作得更好。

请您展示一下您试图模仿的Haskell类型的定义,好吗?我不知道上面的代码与sum类型有什么关系。求和类型有两个构造函数(或N>=2个构造函数,在其N元变量中),以及一个具有2个(或N个)参数的析构函数/消除器。您可能希望查找总和类型的Church编码;首先,它是CPS编码,但不是总和的编码,而是总和的每个变量中的乘积的编码(“总和”是开关);通常被称为“智能构造函数”的东西嵌入在类型定义本身中。您还有一个“type”,它本质上是
或Char String
,但作为
(Bool,String)
编写起来要容易得多,尤其是在JS中。虽然我知道这在“技术上”是一种求和类型,但它在Haskell中不是这样使用的(特别是在类型中嵌入智能构造函数)。是的,将类型与构造函数混合是一个非常糟糕的主意。您应该在JS代码中实现相同的区别。@fta或者类似的东西,也许?它使用一种Church编码,具有一些语法细节。函数式编程包含不变性,因此第一个功能的丢失不应该是一个问题。@4 castle No,“modify the type”指的是编辑源代码以更改其中一种情况下的字段数(例如,意识到我们需要在全名案例中为人员的头衔添加一个额外字段),与运行时值的可变性无关。