我最近一直在从事数字系统转换器的研究,发现这一嵌套函数代码块使我对它的使用提出了疑问。
就我而言,第一个代码输出的结果与第二个代码相同。那么,为什么有人诉诸更复杂的东西呢?这种方法有什么优点?
convertBase(num).numFrom(from).numTo(to);
let convertBase = (num) => {
return {
numFrom: function (baseFrom) {
return {
numTo: function (baseTo) {
}
}
}
}
}
convertBase(num, from, to);
let convertBase = (num, baseFrom, baseTo) => {
return parseInt(num, baseFrom).toString(baseTo);
}
答案 0 :(得分:2)
这与嵌套函数无关,而与schönfinkeling/ currying有关。 Schönfinkeling/ currying以开发此技术的MosesSchönfinkel(在Gottlob Frege先前介绍该技术之后)和Haskell Curry(对其进行完善和描述)的名字命名。
简而言之,currying是一种技术,它允许将 n 自变量的任何函数转换为 n-1 自变量的函数,该函数返回采用< em> nth 参数。通过重复应用这一点,您可以证明,使用任意多个参数对函数建模时,您不需要多个参数的函数。
这里是一个例子。我可以打开一个将两个数字相加的函数:
function add(a, b) { return a + b; }
add(2, 3)
//=> 5
进入返回加法器函数的“加法器工厂”,该加法器函数在被调用时将产生两个数字的和:
function adderFactory(a) {
return function adder(b) { return a + b; };
}
const twoAdder = adderFactory(2);
twoAdder(3)
//=> 5
或
adderFactory(2)(3)
//=> 5
现在,您可能会想:“但是ECMAScript支持带有多个参数的函数,因此,如果我可以原生使用它们,为什么还要使用currying模拟它们?”你会是对的!出于这个原因,使用curring没有任何意义。
但是,您可能还需要对函数做一些有趣的事情:部分应用。 “函数应用程序”只是函数式编程所代表的“调用函数”,因此“部分应用程序”的意思是“仅使用其参数的子集来调用函数”。部分应用程序仅使用其某些参数调用函数,并生成仅针对那些参数 specialized 的函数。在支持部分应用程序的语言中,我可以执行以下操作:
const fourAdder = add(4, ?);
但是,ECMAScript没有部分应用。
但是,当我使用函数时,我可以执行“部分应用排序”,在这里我至少只能提供前几个参数,而忽略最后几个参数。这意味着您必须考虑哪些参数更可能是固定的,哪些参数更可能是可变的,并且应该按“可变性”对其进行排序。
因此,对于您发布的功能,可以创建一个基本转换器,该转换器只能将一个特定的数字从一个特定的基数转换为任意数目的基数。我必须承认,这实际上不是非常有用。如果这样定义函数,它将更加有用:
const convertFromBaseToBase = baseFrom =>
baseTo =>
num => parseInt(num, baseFrom).toString(baseTo);
convertFromBaseToBase(2)(8)('1001')
//=> '11'
现在,例如,您可以像这样创建从八进制到十六进制的转换器:
const octalToHexadecimalConverter = convertFromBaseToBase(8)(16);
octalToHexadecimalConverter('17')
//=> "F"
注意!由于只能“部分地从右侧应用”的限制,实际上,您也可以使用带有默认参数的可选参数来完成此操作,例如:
const baseToToken = Symbol('baseTo'),
numToken = Symbol('num');
function convertFromBaseToBase(baseFrom, baseTo=baseToToken, num=numToken) {
if (num === numToken) {
if (baseTo === baseToToken) {
return (baseTo, num=numToken) =>
num === numToken ?
num => parseInt(num, baseFrom).toString(baseTo) :
parseInt(num, baseFrom).toString(baseTo);
} else {
return num => parseInt(num, baseFrom).toString(baseTo);
}
} else {
return parseInt(num, baseFrom).toString(baseTo);
}
}
convertFromBaseToBase(8, 16, '17')
//=> 'F'
convertFromBaseToBase(8, 16)('17')
//=> 'F'
convertFromBaseToBase(8)(16)('17')
//=> 'F'
convertFromBaseToBase(8)(16, '17')
//=> 'F'
但是,正如您所看到的,这开始变得非常丑陋,快速。
问题中的代码段还有另一个有用的原因:它提供了一个 fluent接口,该接口为特定参数指定了名称,这样您就不会混淆两个数字参数baseFrom
和baseTo
。但是,这也可以通过其他几种方式解决。一种是通过对函数进行命名,以便清楚地知道baseFrom
或baseTo
是第一位的,即代替convertBase(num, baseFrom, baseTo)
来命名为convertNumberFromBaseToBase(num, baseFrom, baseTo)
。另一种可能性是使用对象参数,如下所示:
function convertBase({ num, baseFrom, baseTo }) {
return parseInt(num, baseFrom).toString(baseTo);
}
convertBase({ num: '17', baseFrom: 8, baseTo: 16 })
//=> 'F'
但是,即使使用更具描述性的函数名称或流畅的接口,更改参数顺序仍然有意义,以使currying和部分应用程序更加有用。
还请注意,对于在这种情况下不是的嵌套函数,我什么也没说过,例如在这种情况下,例如[代码改编自Ruby Recursive Indexing/Searching Method (Using Middle Comparison) Returning Incorrect Index Value]:< / p>
function bsearch(arr, target) {
function bsearchRec(arr, target, offset=0) {
const middleIndex = Math.floor(arr.length / 2);
if (arr[middleIndex] === target) { return offset + middleIndex; }
if (arr.length === 1) { return undefined; }
if (target > arr[middleIndex]) {
return bsearchRec(arr.slice(middleIndex+1), target, offset + middleIndex + 1);
} else if (target < arr[middleIndex]) {
return bsearchRec(arr.slice(0, middleIndex), target, offset);
}
}
return bsearchRec(arr, target);
}
bsearch([1, 3, 4, 5, 9], 5)
//=> 3
这里,嵌套函数bsearchRec
嵌套在bsearch
内,因为它是bsearch
的私有内部实现细节,除bsearch
的作者以外,其他任何人都不应该知道它。
最后,功能是ECMAScript中用于封装的工具。特别是,函数是ECMAScript实现对象的方式。对象具有由名称和封装标识的行为。在大多数OO语言中,行为,封装和名称到行为的映射(又称“方法调用”)这三件事是由一个实体(即对象)提供的。在ECMAScript中,封装是由函数(闭包)提供的,行为是由函数提供的(嵌套在闭包内部以共享私有状态的),而名称到行为的映射是由字典提供的,它们被混淆地称为 objects ,即使它们只实现了成为对象的含义的三分之一。
因此,没有嵌套函数,ECMAScript中将没有封装,最重要的是,没有对象!甚至模块和类在嵌套函数之上也主要是语法糖。
答案 1 :(得分:1)
答案 2 :(得分:0)
它提供了一个fluent interface,可清楚说明哪个值到达何处。
convert(3).fromBase(16).toBase(2);
绝对比(p>更好(更易于维护,更具可读性,更少出错))
convertBase(3, 16, 2);
3个整数参数的顺序不明显。