【问题标题】:Functional datatypes in javascriptjavascript中的函数数据类型
【发布时间】:2012-06-29 01:00:35
【问题描述】:

如何在 javascript 中构造函数式和递归数据类型?

我希望能够做一些 ML 之类的事情:

datatype binary_node = Node of binary_node*binary_node 
                     | Lead of int

前段时间我参加了一门函数式编程课程——出于某种随机原因,该课程是在 Scheme 中,我们通过制作 tubles 来构造数据类型,从数据类型的名称开始,然后是“有效负载”,这是在 Javascript 中处理函数式编程风格数据类型的方法吗?

construct_node(n1,n2) -> 
    ("Node", n1, n2).

construct_leaf(int_value) ->
    ("Leaf", int_value).

然后是类型检查器:

is_node(n) ->
    if (n[0] == "Node") ->
        is_binary_tree(n[1])  and is_binary_tree(n[2])
    else
        false
is_leaf(l) ->
    if(l[0] == "Leaf") ->
        is_integer(n[1])
    else 
        false
is_binary_tree(t) ->
    is_node(t) or is_leaf(t)

在 javascript 中最聪明的方法是什么?

【问题讨论】:

    标签: javascript types functional-programming


    【解决方案1】:

    JavaScript 通常以duck typed 的方式使用。这样,您不需要定义任何特殊的数据类型。任何具有node1node2 属性的对象都可以被视为二叉树节点。

    var n = {
        node1: {
            node1: 456,
            node2: 789
        },
        node2: 1002
    };
    
    function isNode(x) {
        return x && isBinaryTree(x.node1) && isBinaryTree(x.node2);
    }
    
    function isLeaf(x) {
        return typeof x === 'number';
    }
    
    function isBinaryTree(x) {
        return isNode(x) || isLeaf(x);
    }
    

    请注意,上述函数用于递归检查整个树的完整性,而不是在遍历期间逐个节点区分大小写。

    【讨论】:

    • 谓词的成本与树的大小成线性关系,这将进行遍历,您检查每个节点的成本呈指数增长。 (另外,我建议避免使用诸如“duck typing”之类的无意义术语。)
    • 我喜欢duck-type这个词,以后我会在论文中使用它:-)
    • @AndreasRossberg 我不确定你为什么要在遍历同一棵树时检查树的每个节点。你能举一个实际的例子吗?另外,我不同意“duck typing”——我认为这是一个非常有意义(如果不是很精确)的术语——但我很高兴听到你对术语的建议。
    • @MartinKristiansen 你只是忽略了那个额外的node 属性,就像你忽略了像toString 这样的内置属性。只要有node1node2,就是二叉树节点。
    • 请注意,我(和您)在谈论鸭子打字作为语言属性,而不是使用模式。 (我也不同意自定义属性或实现“类型”的实例,因为两者都会产生大量误报。)
    【解决方案2】:

    我见过的一种方法是为代数数据类型的每种情况创建一个构造函数,并使用 instanceof 测试来实现分支。例如,这就是Roy language 实现模式匹配和标记联合的方式。

    var Node = function(a, b){
        //protect against people forgetting to use "new" to call the constructor:
        if(!(this instanceof Node)){ return new Node(a, b) }
    
        //optionaly do extra type checking, if that is your thing:
        if(!(a instanceof Leaf)){ /*...*/ }
        if(!(b instanceof Leaf)){ /*...*/ }
    
        //save properties
        this.node1 = a;
        this.node2 = b;
    };
    
    var Leaf = function(value){
        /*...*/
        this.value = value;
    };
    

    这样,内部“__proto__”属性的标记和有效负载是对象中通常的实例属性。

    我喜欢这种方法的一个原因是它非常“类型安全”。内部原型属性是不可编辑的,并且使用构造函数或对象(而不是符号或字符串)可以减少名称冲突,无论是与不同类型还是与对象的属性。

    另一个好处是,与依赖于鸭子类型不同,这种方法还可以无缝地处理枚举和其他不同情况具有相同属性集的情况。

    关于 Javascript 的一个坏处是,与 LISP 的情况一样,它没有内置对解构的支持,因此您可能需要创建一个自定义匹配函数,如下所示。

    var match_tree = function(x, cases){
        //just a helper function to make things pretty
        if(x instanceof Node){
            return cases.node.call(this, x.node1, x.node2);
        }else if(x instanceof Leaf){
            return cases.leaf.call(this, x.value);
        }else{
            throw new TypeError("pattern match failed");
        }
    };
    
    var sum_leaves = function(tree){
        return match_tree(tree, {
            node: function(val){ return val },
            leaf: function(left, right){
               return sum_leaves(left) + sum_leaves(right);
            }
        });
     };
    

    【讨论】:

      【解决方案3】:

      我是一种名为Roy 的语言的创造者。 Roy 是一种具有静态类型、代数数据类型和模式匹配的语言。它还可以编译为 JavaScript。

      您的示例将如下所示:

      data Tree = Node Tree Tree | Leaf Number
      

      数字是内置的 JavaScript 类型。

      现在我们可以在 ADT 上进行模式匹配:

      let isNode n = match n
        case (Node a b) = true
        case (Leaf l) = false
      
      let isLeaf l = match l
        case (Leaf l) = true
        case (Node a b) = false
      

      这是 JavaScript 输出:

      var Node = function(Tree_0, Tree_1) {
          if(!(this instanceof Node)) {
              return new Node(Tree_0, Tree_1);
          }
          this._0 = Tree_0;
          this._1 = Tree_1;
      };
      var Leaf = function(Number_0) {
          if(!(this instanceof Leaf)) {
              return new Leaf(Number_0);
          }
          this._0 = Number_0;
      };
      var isNode = function(n) {
          return (function() {
              if(n instanceof Node) {
                  var a = n._0;
                  var b = n._1;
                  return true;
              } else if(n instanceof Leaf) {
                  var l = n._0;
                  return false;
              }
          })();
      };
      var isLeaf = function(l) {
          return (function() {
              if(l instanceof Leaf) {
                  var l = l._0;
                  return true;
              } else if(l instanceof Node) {
                  var a = l._0;
                  var b = l._1;
                  return false;
              }
          })();
      };
      

      【讨论】:

      • 我的朋友 Kasper 向我介绍了您的 Roy,一些关于 Monads 和 do-notation 的信息——但 Roy 是一种编译成 javascript 的语言?
      • 是的,它是专门为编译成 JS 而设计的
      • 它可以编译成其他语言吗? IE。服务器端语言?
      • 有一个 fork 将 Roy 编译成 Ruby。没有积极维护,但我对如何让 Roy 轻松支持更多目标有一些想法。
      猜你喜欢
      • 1970-01-01
      • 2019-06-03
      • 2016-12-28
      • 2016-05-13
      • 2014-09-30
      • 2021-09-09
      • 1970-01-01
      • 2017-01-09
      • 1970-01-01
      相关资源
      最近更新 更多