我刚刚开始研究不同的编程风格(OOP,功能,程序)。
我正在学习JavaScript并开始使用underscore.js并且在文档中出现了this小部分。 文档说下划线.js可以用于面向对象或功能样式,并且这两者都可以产生相同的结果。
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
我不明白哪一个是功能性的,哪一个是OOP,即使经过对这些编程范式的一些研究,我也不明白为什么。
答案 0 :(得分:26)
面向对象编程(OOP)和功能编程(FP)是编程范例。粗略地说,遵循编程范式是编写符合特定规则集的代码。例如,将代码组织成单元称为OOP,避免副作用称为FP。
每种编程范例都由特定功能组成,但是您最喜欢的语言不必将所有功能都归入一个范例。事实上,OOP可以没有inheritance或encapsulation,因此我们可以说JavaScript(JS)是一种带有继承且没有封装的OOP语言。
既然你已经对编程范式有所了解(希望如此),那么让我们快速浏览一下OOP和FP的基础知识。
在OOP中,对象是一个包含信息和操作的框,应该引用相同的概念。信息通常称为“属性”,操作通常称为“方法”。属性允许跟踪对象的状态,方法允许操纵对象的状态。
在JS中,您可以向对象发送消息以执行特定方法。以下代码是JS中message passing的示例。它显示了一个“点”对象,它有两个属性,“x”和“y”,以及一个名为“translate”的方法。 “translate”方法根据给定的向量更新“point”的坐标。
point = {
x: 10, y: 10,
translate: function (vector) {
this.x += vector.x;
this.y += vector.y;
}
};
point.x; // 10
point.translate({ x: 10, y: 0 });
point.x; // 20
这种简单案例涉及的功能并不多。在OOP中,代码通常被划分为类,并且通常支持继承和多态。但我不会进一步详述,因为我担心我已经超出了你的问题的范围。
在FP中,代码本质上是函数的组合。而且,数据是不可变的,这导致编写程序没有副作用。在功能代码中,函数无法更改外部世界,输出值仅取决于给定的参数。这样可以保持对程序流程的强大控制。
实际上,只要你处理副作用,JS就可以用作FP语言,没有内置机制。以下代码是这种编程风格的示例。 “zipWith”功能来自Haskell世界。它使用给定的函数合并两个列表,add(point[i], vector[i])
。
zipWith = function (f, as, bs) {
if (as.length == 0) return [];
if (bs.length == 0) return [];
return [f(as[0], bs[0])].concat(
zipWith(f, as.slice(1), bs.slice(1))
);
};
add = function (a, b) {
return a + b;
};
translate = function (point, vector) {
return zipWith(add, point, vector);
};
point = [10, 10];
point[0]; // 10
point = translate(point, [10, 0]);
point[0]; // 20
这个定义虽然很肤浅。例如,Haskell是一种纯函数式语言,它实现了更多的概念,如函数组合,仿函数,currying,monad等等......这使得代码更加简洁和优雅。
实际上,OOP和FP是两个没有共同点的不同概念,我甚至会说没有什么可比较的。因此,我相信你从Underscore.js文档中读到的是对语言的误用。
您不应该在此库的范围内学习编程范例。实际上,使用Underscore.js编写代码的方式使其类似于OOP和FP,但这只是外观问题。因此,引擎盖下没有什么令人兴奋的事情: - )
请参阅维基百科深入阅读。
答案 1 :(得分:11)
功能:您将对象传递给函数并执行操作
_.map([1, 2, 3], function(n){ return n * 2; });
OOP:您在对象上调用函数并执行操作
_([1, 2, 3]).map(function(n){ return n * 2; });
在两个示例中,[1,2,3] (array)
都是一个对象。
示例OOP参考:http://underscorejs.org/#times
答案 2 :(得分:8)
对于什么是“功能”而不是“功能”没有正确的定义,但通常功能语言强调数据和功能的简单性。
大多数函数式编程语言都没有属于对象的类和方法的概念。函数在明确定义的数据结构上运行,而不是属于数据结构。
第一个样式_.map
是_
命名空间中的一个函数。它是一个独立的函数,您可以将其返回或作为参数传递给另一个函数。
function compose(f, g) {
return function(data) {
return f(g(data));
}
}
const flatMap = compose(_.flatten, _.map);
对于第二种样式,不可能这样做,因为方法实例本质上与用于构造对象的数据相关联。所以我要说第一种形式是更多功能。
在任何一种情况下,一般的函数式编程风格是数据应该是函数的最后一个参数,这使得更容易理解或部分应用早期的参数。 Lodash/fp和ramda通过为地图添加以下签名来解决此问题。
_.map(func, data);
如果函数是curry,你可以通过传递第一个参数来创建函数的特定版本。
const double = x => x * 2;
const mapDouble = _.map(double);
mapDouble([1, 2, 3]);
// => [2, 4, 6]
答案 3 :(得分:2)
FP
在FP中,函数接受输入并产生输出,并确保相同的输入将产生相同的输出。为此,函数必须始终为其操作所依据的值具有参数,并且不能依赖状态。即,如果一个函数依赖状态,并且状态发生变化,则该函数的输出可能会不同。 FP不惜一切代价避免这种情况。
我们将显示FP和OOP中map
的最低实现。在下面的此FP示例中,请注意map
如何仅对局部变量进行操作,而不依赖状态-
const _ = {
// ? has two parameters
map: function (arr, fn) {
// ? local
if (arr.length === 0)
return []
else
// ? local
// ? local // ? local // ? local
return [ fn(arr[0]) ].concat(_.map(arr.slice(1), fn))
}
}
const result =
// ? call _.map with two arguments
_.map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
以这种样式,将map
存储在_
对象中没关系-因为使用了一个对象,所以它没有变成“ OOP”。我们本来可以写得很容易-
function map (arr, fn) {
if (arr.length === 0)
return []
else
return [ fn(arr[0]) ].concat(map(arr.slice(1), fn))
}
const result =
map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
这是FP中通话的基本方法-
// ? function to call
// ? argument(s)
someFunction(arg1, arg2)
对于FP来说,值得注意的是map
有两个(2)参数arr
和fn
,而map
的输出仅取决于这些输入。您将在下面的OOP示例中看到这种情况的变化。
OOP
在OOP中,对象用于存储状态。调用对象的方法时,该方法(函数)的上下文被动态地 绑定到接收对象,即this
。由于this
是一个 changing 值,因此即使给出了相同的输入,OOP也不能保证任何方法都将具有相同的输出。
请注意,map
如何仅接受下面的一(1)个自变量fn
。我们如何仅使用map
fn
?我们将map
做什么?如何为map
指定目标? FP认为这是一场噩梦,因为该函数的输出不再仅取决于其输入-现在更难以确定map
的输出,因为它取决于{{1 }}-
this
这是在OOP中进行动态调用的基本方法-
// ? constructor
function _ (value) {
// ? returns new object
return new OOP(value)
}
function OOP (arr) {
// ? dynamic
this.arr = arr
}
// ? only one parameter
OOP.prototype.map = function (fn) {
// ? dynamic
if (this.arr.length === 0)
return []
else // ? dynamic // ? dynamic
return [ fn(this.arr[0]) ].concat(_(this.arr.slice(1)).map(fn))
}
const result =
// ? create object
// ? call method on created object
// ? with one argument
_([1, 2, 3]).map(function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
重新访问FP
在第一个FP示例中,我们看到// ? state
// ? bind state to `this` in someAction
// ? argument(s) to action
someObj.someAction(someArg)
和.concat
-这些OOP动态调用不是吗?它们是,但是特别是这些不会修改输入数组,因此可以安全地用于FP。
话说回来,各种风格的呼唤可能会让人有些眼花ore乱。 OOP赞成在方法的参数之间中显示方法(函数)的“中缀”表示法-
.slice
这也是JavaScript的运算符的工作方式-
// ? arg1
// ? function
// ? arg2
user .isAuthenticated (password)
FP倾向于使用“前缀”表示法,其中函数始终位于其参数之前。在理想的世界中,我们可以在 any 位置调用OOP方法和运算符,但不幸的是JS不能这样工作-
// ? arg1
// ? function
// ? arg2
1 + 2
通过将// ? invalid use of method
.isAuthenticated(user, password)
// ? invalid use of operator
+(1,2)
和.conat
之类的方法转换为函数,我们可以以更自然的方式编写FP程序。请注意,前缀表示法的一致使用如何使您更容易想象计算如何进行-
.slice
方法转换如下-
function map (arr, fn) {
if (isEmpty(arr))
return []
else
return concat(
[ fn(first(arr)) ]
, map(rest(arr, 1), fn)
)
}
map([1, 2, 3], function(n){ return n * 2; })
// => [ 2, 4, 6 ]
这开始显示FP的其他优势,在这些优势中,功能保持较小并专注于一项任务。并且由于这些功能仅在其输入上起作用,因此我们可以轻松地在程序的其他区域中重用它们。
您的问题最初是在2016年提出的。此后,现代JS功能使您可以用更优雅的方式编写FP-
function concat (a, b) {
return a.concat(b)
}
function first (arr) {
return arr[0]
}
function rest (arr) {
return arr.slice(1)
}
function isEmpty (arr) {
return arr.length === 0
}
使用expressions代替statements进行进一步细化-
const None = Symbol()
function map ([ value = None, ...more ], fn) {
if (value === None)
return []
else
return [ fn(value), ...map(more, fn) ]
}
const result =
map([1, 2, 3], function(n){ return n * 2; })
console.log(result)
// [ 2, 4, 6 ]
语句依赖于副作用,而表达式直接求值。表达式在代码中留下的潜在“漏洞”较少,在这些漏洞中,语句可以随时执行任何操作,例如抛出错误或退出函数而不返回值。
带有对象的FP
FP并不意味着“不要使用对象”,而是要保留能够轻松推理程序的能力。我们可以编写相同的const None = Symbol()
const map = ([ value = None, ...more ], fn) =>
value === None
? []
: [ fn(value), ...map(more, fn) ]
const result =
map([1, 2, 3], n => n * 2)
console.log(result)
// [ 2, 4, 6 ]
程序,以产生我们在使用OOP的错觉,但实际上,它的行为更像FP。它看起来像方法调用,但是实现仅依赖于局部变量,而不依赖于动态状态(map
)。
JavaScript是一种丰富,富有表现力的多范式语言,可让您编写适合自己需求和偏好的程序-
this
答案 4 :(得分:1)
严格来说,他们都是OOP。但是,第一种方法(_.map
)也称为效用函数。该函数是_
对象的方法,但该语句不会创建_
构造函数的新实例,并且在方法调用之后返回值。由于返回的值是一个Array对象,您还可以调用它上面的所有数组方法。这意味着它仍然是OOP,因为在JavaScript中几乎所有东西都是对象。
第二个创建_
对象的新实例,它使方法(下划线对象的方法)可链接,您应该使用value
方法获取包装值。
该段落本质上意味着在第一个片段中没有创建下划线对象,而在第二个片段中,在场景后面创建了一个下划线对象。
这里的OOP /功能是指样式,而不是JavaScript开发人员的实用视角。
答案 5 :(得分:0)
两个UIAlertController
都有效,两个代码都基于concpet,value =>地图功能的价值。
但是,两者都可以看到OOP,因为object.map风格。
我不会想要通过Underscore了解函数式编程。