在道格拉斯Crockford的书"Javascript:好的部分"他提供了代码 curry 方法需要一个功能和参数和返回这一职能与参数已经增加(显然,这不是真的 什么"咖喱"的意思, 但是一个例子 "部分程序").这里的代码,我已经修改,以便它没有一些其他代码定义,他提出:

Function.prototype.curry = function(){
  var slice = Array.prototype.slice,
      args = slice.apply(arguments),
      that = this;
  return function() {
    // context set to null, which will cause `this` to refer to the window
    return that.apply(null, args.concat(slice.apply(arguments)));
  };
};

所以如果你有一个 add 功能:

var add = function(num1, num2) {
  return num1 + num2;
};

add(2, 4);          // returns 6

你可以做一个新的功能,已经有一个参数:

var add1 = add.curry(1);

add1(2);           // returns 3

工作的罚款。但我想知道的是他为什么设置的 thisnull?不会预期的行为可扩充的方法是相同的原件,包括相同的 this?

我的版本的咖喱会是这样的:

Function.prototype.myCurry = function(){
  var slice = [].slice,
      args = slice.apply(arguments),
      that = this;
  return function() {
    // context set to whatever `this` is when myCurry is called
    return that.apply(this, args.concat(slice.apply(arguments)));
  };
};

(这里是一个jsfiddle的例子)

var calculator = {
  history: [],
  multiply: function(num1, num2){
    this.history = this.history.concat([num1 + " * " + num2]);
    return num1 * num2;
  },
  back: function(){
    return this.history.pop();
  }
};

var myCalc = Object.create(calculator);
myCalc.multiply(2, 3);         // returns 6
myCalc.back();                 // returns "2 * 3"

如果我尝试这样做,道格拉斯Crockford的方式:

myCalc.multiplyPi = myCalc.multiply.curry(Math.PI);
myCalc.multiplyPi(1);          // TypeError: Cannot call method 'concat' of undefined

如果我这样做我的方式:

myCalc.multiplyPi = myCalc.multiply.myCurry(Math.PI);
myCalc.multiplyPi(1);          // returns 3.141592653589793
myCalc.back();                 // returns "3.141592653589793 * 1"

然而,我觉得如果斯Crockford有没有其他办法,他也许有很好的理由。我是什么丢失?

有帮助吗?

解决方案

原因1-不容易提供一般的解决方案

问题是,你的解决方案不是一般。如果呼叫者不赋予新的功能以任何目的,或将其指定给一个完全不同的目的,你的 multiplyPi 功能将会停止工作:

var multiplyPi = myCalc.multiply.myCurry(Math.PI);
multiplyPi(1);  // TypeError: this.history.concat is not a function

所以,无论是Crockford的也不是你的解决方案可以保证的功能将被正确地使用。然后它可以容易地说, curry 功能仅在"职能",而不是"方法",并设置 thisnull 为力。我们可能只是推测的,因为Crockford没有提到,在本书。

原因的2职能解释

如果你问"为什么Crockford没有使用这个或那个"-非常可能的答案是: "这不是重要的,在关于证明问题。" Crockford使用这个例子中的一章 功能.目的分章 curry 为:

  • 为显示这一职能是对象可以创建和操纵
  • 以证明的另一个使用关闭
  • 以显示如何参数可以操纵。

微调这一一般使用的对象不是目的这一章。因为它是有问题的,如果不甚至是不可能的(见的原因,1),这是更多的教育放在那里只是 null 相反,如果把有 的东西 这可能提出的问题,如果它实际工作,或者没有(没有帮助你的情况虽然:-)).

结论

这就是说,我认为你可以完全相信,在你的解决方案。有没有特别的原因在你的情况下遵循的康乐福的决定,以重置 thisnull. 你必须要知道,虽然这一解决方案只有在某些情况下,并不是100%的净。然后清洁"面向对象的"解决办法是 要问对象 创建一个克隆的方法内部本身, 确保所得的方法将保持在相同的对象。

其他提示

读者小心,你是在一种恐慌。

还有很多要谈的时候扩充、职能、局部应用程序,并对象的取向在JavaScript。我会试着保持这个回答尽可能短暂但有很多的讨论。因此,我必须结构化,我的文章成几个部分和在结束每一个我们总结每个部分对于这些你们太急于宣读这一切。


1.以咖喱或不要咖喱

让我们来谈谈Haskell.在Haskell的每一个功能是咖喱的默认。例如,我们可以创建一个 add 功能在Haskell如下:

add :: Int -> Int -> Int
add a b = a + b

注意到类型的签名 Int -> Int -> Int?这意味着 add 需要一个 Int 和返回的一个功能的类型 Int -> Int 这反过来需要一个 Int 和返回一个 Int.这让你可以部分适用于职能在Haskell容易:

add2 :: Int -> Int
add2 = add 2

同样的功能在JavaScript会看丑:

function add(a) {
    return function (b) {
        return a + b;
    };
}

var add2 = add(2);

这里的问题是,职能JavaScript不是咖喱的默认。你需要手动咖喱他们这是一个痛苦。因此,我们使用的局部应用程序(又名 bind)代替。

第1课: 扩充用于做更容易的部分应用功能。然而这只是有效的语文功能是咖喱默认情况下(例如Haskell).如果你必须手动咖喱功能,那么最好使用局部应用程序替代。


2.该结构的功能

Uncurried功能也存在Haskell.他们看起来像职能"正常"编程语言:

main = print $ add(2, 3)

add :: (Int, Int) -> Int
add(a, b) = a + b

你可以转换成一种能在其扩充的形式对其uncurried形式,反之亦然使用 uncurrycurry 职能Haskell的分别。一个uncurried功能在Haskell仍然只需要一个参数。但是这一论点是一种产品的多个值(即一个 产品类型).

在同样的功能在JavaScript也只需要一个参数(它只是不知道它)。这一论点是一个产品类型。的 arguments 值内的一个函数是一种表现,产品类型。这是举例说明通过 apply 方法在JavaScript这需要一个产品类型和适用的功能。例如:

print(add.apply(null, [2, 3]));

你可以看到相似之间的上述行JavaScript和以下行Haskell?

main = print $ add(2, 3)

忽略的分配 main 如果你不知道什么用的.这是无关紧要的恰当的主题。重要的是,tuple (2, 3) 在Haskell是同形阵列 [2, 3] 在JavaScript。我们怎么从学习这个?

apply 功能在JavaScript相同功能的应用程序(或 $)在Haskell:

($) :: (a -> b) -> a -> b
f $ a = f a

我们采取一种功能的类型 a -> b 并将其应用于一个值的类型 a 获得价值的类型 b.然而,由于所有功能在JavaScript uncurried通过默认的 apply 功能总是需要一个产品类型(即阵列)作为其第二个论点。这就是说,值的类型 a 实际上是一个产品类型在JavaScript。

第2课: 所有功能在JavaScript仅采取单一的参数,这是一个产品类型(即的 arguments 值)。这是否意或偶然是一个问题的猜测。然而重要的一点是,你知,在数学上的每一个功能,只需要一个参数。

用数学的一种功能定义为 态射: a -> b.它需要一个值的类型 a 和返回的一个值的类型 b.一态射只能有一个参数。如果你想多个参数,然后你可以:

  1. 回返的另一种态射(即 b 是另一种态射).这是讨好.Haskell这样做。
  2. 定义 a 是一个产品的多种类型(即 a 是一个产品类型)。JavaScript这样做。

出两个我喜欢咖喱的功能,因为他们作出的局部应用程序的微不足道的。局部应用程序的"uncurried"功能更加复杂。不难,你介意,但只有更为复杂。这是一个原因为什么我喜欢Haskell多JavaScript:功能是咖喱的默认。


3.为什么面向对象的事项不

让我们看看一些面向对象的代码在JavaScript。例如:

var oddities = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].filter(odd).length;

function odd(n) {
    return n % 2 !== 0;
}

现在你可能不知道这是怎么面向对象的。它看起来更像是功能性的代码。之后所有你可能做同样的事情在Haskell:

oddities = length . filter odd $ [0..9]

尽管如此上述码是面向对象的。阵列文字是一个对象,它有一个方法 filter 其返回一个新的阵列的对象。然后我们只是访问 length 新的阵列的对象。

我们怎么从学习这个?链接操作的面向对象的语言是相同的组成职能的功能性的语言。唯一的区别是,该功能码读取倒退。让我们来看看为什么。

在JavaScript的 this 参是特殊的。这是独立于正式函数的参数这就是为什么你需要指定一个价值为它单独在 apply 法。因 this 来之前,正式的参数,方法是锁从左到右。

add.apply(null, [2, 3]); // this comes before the formal parameters

如果 this 进来之后正式参数以上的代码就可能读为:

var oddities = length.filter(odd).[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

apply([2, 3], null).add; // this comes after the formal parameters

不是很漂亮,是吗?那么为什么功能在Haskell读倒退?答案是讨好.你看到的功能在Haskell还有一个"this"参数。然而,与在JavaScript的 this 参数在Haskell不是特别的。此外,它在结束的参数清单。例如:

filter :: (a -> Bool) -> [a] -> [a]

filter 功能需要谓的功能和一个 this 列出和返回一个新的清单只用过滤的要素。所以为什么 this 参去?它使得局部应用程序更加容易。例如:

filterOdd = filter odd
oddities = length . filterOdd $ [0..9]

在JavaScript你会写:

Array.prototype.filterOdd = [].filter.myCurry(odd);
var oddities = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].filterOdd().length;

现在你会选择哪一个?如果你还抱怨阅读后退后我有消息给你。你可以让Haskell的代码阅读转发使用"后应用程序"和"反向组合物"如下:

($>) :: a -> (a -> b) -> b
a $> f = f a

(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
f >>> g = g . f

oddities = [0..9] $> filter odd >>> length

现在你有两个最好的世界。你的代码读的前锋,你得到所有的好处的讨好.

有很多问题 this 那不会发生在功能语言:

  1. this 参数是专门。不像其他的参数不能简单地将它设置的任意的对象。因此需要使用 call 指定一个不同的价值 this.
  2. 如果你想以部分适用于职能在JavaScript然后你需要的指定 null 作为第一个参数的 bind.同样用于 callapply.

面向对象的程序没有任何与 this.其实你可以写面向对象的代码在Haskell。我会尽说,Haskell事实上是面向对象编程语言,并且一个更好的之一在于Java或C++。

第3课: 功能编程语言是更多的面向对象的比大多数主流面向对象编程语言。实际上面向对象的代码在JavaScript将更好地(虽然不可否认的可读性较差)如果写入一个功能性的风格。

该问题与面向对象的代码在JavaScript是的 this 参数。在我看来的 this 参数不应该处理任何不同于正式的参数(Lua得到了这个右)。的问题 this 是的是:

  1. 有没有办法设置 this 像其他正式的参数。你必须使用 call 代替。
  2. 你必须设置 thisnullbind 如果你希望仅部分地适用一个功能。

在一个侧面说明我刚刚意识到,每一部分的这一条变得更长,比前一部分。因此,我保证下一个(以及最终)段尽可能短。


4.在防御的道格拉斯Crockford

现在你必须拿起,我认为大多数的JavaScript被打破的,你应该转移到Haskell的代替。我喜欢相信道格拉斯Crockford是一个功能程序过,他是在试图解决JavaScript。

我怎么知道他的功能程序员?他的家伙,即:

  1. 推广的功能等同的 new 关键词(。k.一个 Object.create).如果你不已经这样做了,那么你应该 停止使用 new 关键字.
  2. 试图解释这一概念的 异性腺 JavaScript社区。

无论如何,我认为Crockford无效 thiscurry 因为他知道怎么糟糕 this 是。这将是亵渎集到的任何其他 null 在一本书,题为"JavaScript:好的部分"。我认为他是使世界成为一个更好的地方的一个特征,在一段时间。

通过取消 this Crockford是迫使你停止依靠它。


编辑: 作为Bergi要求我将描述一个多功能性的方式来写你的面向对象 Calculator 代码。我们将使用Crockford的 curry 法。让我们开始的 multiplyback 职能:

function multiply(a, b, history) {
    return [a * b, [a + " * " + b].concat(history)];
}

function back(history) {
    return [history[0], history.slice(1)];
}

正如你可以看到的 multiplyback 功能不属于任何目的。因此你可以用它们在任何阵列。特别是你 Calculator 类只是一个包装串名单。因此,你甚至都不需要创造一个不同的数据类型。因此:

var myCalc = [];

现在你可以使用Crockford的 curry 方法的局部应用程序:

var multiplyPi = multiply.curry(Math.PI);

接下来我们将创建一个 test 功能 multiplyPi 通过一个和回到以前的状态:

var test = bindState(multiplyPi.curry(1), function (prod) {
    alert(prod);
    return back;
});

如果你不喜欢的语法然后你可以切换到 LiveScript:

test = do
    prod <- bindState multiplyPi.curry 1
    alert prod
    back

bindState 功能是 bind 功能的状态单.它的定义如下:

function bindState(g, f) {
    return function (s) {
        var a = g(s);
        return f(a[0])(a[1]);
    };
}

因此,让我们把它放到测试:

alert(test(myCalc)[0]);

看到这里的演示: http://jsfiddle.net/5h5R9/

顺便说一句,这是整个程序已经更为简洁,如果写入LiveScript如下:

multiply = (a, b, history) --> [a * b, [a + " * " + b] ++ history]

back = ([top, ...history]) -> [top, history]

myCalc = []

multiplyPi = multiply Math.PI

bindState = (g, f, s) -->
    [a, t] = g s
    (f a) t

test = do
    prod <- bindState multiplyPi 1
    alert prod
    back

alert (test myCalc .0)

看到演示的编制LiveScript码: http://jsfiddle.net/5h5R9/1/

所以,这是怎么代码面向对象?维基百科的定义 面向对象编程 为:

面向对象的程序(接力)是一种编程的范例,表示概念,如"对象",有数据的领域(特性描述的对象)和相关的程序,称为方法。对象,这通常是类的实例,被用于与另一个设计应用程序和计算机程序。

根据这一定义功能编程语言,如Haskell是面向对象的,因为:

  1. 在Haskell,我们代表的概念 代数数据类型 这基本上是"对象类固醇"。一个安达泰具有一个或多个构造,这可能有零或更多的数据字段。
  2. 位于Haskell有关的职能。然而不同于主流对象编程语言的位没有自己的职能。而不是职能专注在位.这实际上是一件好事,因为位是开放的增加更多的方法。在传统的面向对象的语言,如爪哇和C++它们是关闭的。
  3. 位可以作出的实例typeclasses类似的接口。因此,你仍然没有继承、差异和子类型的多态性的,但在一个更侵入性的形式。例如 Functor 是一个超类的 Applicative.

上述代码也是面向对象的。对象在这种情况下是 myCalc 这简直是一个阵列。它有两个相关的功能有: multiplyback.然而,它并不拥有这些职能。正如你可以看到的"功能"面向对象的编码具有以下优点:

  1. 对象不自己的方法。因此很容易联系的新的功能要对象。
  2. 局部应用程序是作了简单的经讨好.
  3. 它促进了通用方案编制。

因此,我希望帮助。

但我想知道的是为什么他这样对空?

那里是不是真的一个原因。也许他想到了简化,并且大多数职能的有意义的是咖喱或部分施加不是面向对象的方法,使用 this.在一个多功能的风格 history 阵列,附于会被另一个参数的功能(也许甚至一个返回值)。

不会预期的行为可扩充的方法是相同的原件,包括相同的这个?

是的,您的实施使得更多的意义,但是一个不可能期望一个部分适用的功能仍然需要在正确的背景下(因为你做的通过重新分配到你的目的),如果它使用一个。

对于那些,你能看一看 bind 方法 功能物体的局部应用程序包括一个特定的 this-值。

MDN:

时传递的价值此提供呼叫中的乐趣。注意这个 可能不是实际价值的看法:如果的方法是 功能在非严格的方式码,null和未定义将会被替换 与全球对象,原始价值观将会被装箱。

因此,如果的方法是,在不严格的模式的第一个论点是 nullundefined, this 里面的那个方法将基准 Window.在严格的模式,这是 nullundefined.我已经增加了一个活生生的例子上 这小提琴.

此外,通过在 nullundefined 不会做任何伤害情况下的功能不准 this 在所有。也许这就是为什么Crockford用 null 在他的例子,不过于复杂的事情。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top