功能编程是否具有此逻辑的标准构造?
const passAround = (f) => (x) => {
f(x);
return x;
};
这使我能够构成具有副作用且无返回值的函数,例如console.log
。这不是一个任务,因为我不想表示副作用的状态。
如果您在谈论纯粹的功能编程,那么您需要挑战这个起点:
具有副作用且无返回值的函数
在功能编程中,没有这样的东西。每个功能都定义为某些输入中的转换。
因此,显而易见的问题是,您如何代表console.log
而没有副作用?要回答,我们需要在您的问题中挑战另一个假设:
我不想表示副作用的状态
这正是功能编程如何代表问题的方式:将您的输入和输出视为"世界状态"。换句话说,鉴于功能之前的世界状态,请在功能之后返回世界状态。在这种情况下,您将表示控制台的状态:给定带有x输出行的控制台,返回具有X 1行输出行的控制台。粗鲁,您可以写这样的东西:
(x, console) => { return [x, console.withExtraLine(x)]; }
通常用于表示这一点的更强大的机制称为" monad" - 一种特殊的对象,它包裹了一系列步骤,并具有一些额外的含义。在IO
单元的情况下,每个步骤都包裹着将改变世界状态的动作。(I/O只是Monad概念的许多有用应用之一。)
您将步骤写为函数,这些函数仅知道该状态某些部分的"未包装"值(例如,最终来自用户输入的参数),而单调处理了实际执行该的混乱细节功能程序的领域之外。因此,与其将您的输入和输出视为"世界状态",不如将您的输入视为"计算链",而输出是"稍长的计算链"。
对此进行了许多介绍,远胜于我所能提供的要好得多,只需搜索" Monad"或"功能性编程IO"。
另请参见此答案,此问题,以及当您查看此问题时,"相关"侧栏自动生成中的其他许多其他问题。
滑雪组合器演算可能会让您感兴趣。让我们假装f
始终是一个纯粹的功能:
const S = g => f => x => g(x)(f(x)); // S combinator of SKI combinator calculus
const K = x => y => x; // K combinator of SKI combinator calculus
const passAround = S(K); // Yes, the passAround function is just SK
console.log(passAround(console.log)(10) + 20);
无论如何,我出现滑雪组合器演算的原因是因为我想向您介绍应用函数的概念。特别是,Reader
应用函子等效于滑雪量组合器。S
组合器等效于Reader
的ap
方法,K
组合器等效于Reader
的pure
方法。
在JavaScript中,相当于Reader
的 Function
。因此,我们可以在JavaScript中定义ap
和pure
,如下所示:
Function.prototype.ap = function (f) {
return x => this(x)(f(x));
};
Function.pure = x => y => x;
const print = Function.pure.ap(console.log);
console.log(print(10) + 20);
但是,等等,您可以使用应用程序函数做更多的事情。每个应用函子也是函子。这意味着应用函子还必须具有map
方法。对于Reader
,map
方法只是功能组成。它等效于B
组合器。使用map
您可以做真正有趣的事情:
Function.prototype.ap = function (f) {
return x => this(x)(f(x));
};
Function.pure = x => y => x;
const id = x => x; // I combinator of SKI combinator calculus
Function.prototype.map = function (f) {
return x => this(f(x));
};
Function.prototype.seq = function (g) {
return Function.pure(id).map(this).ap(g);
};
const result = console.log.seq(x => x + 20);
console.log(result(10));
seq
功能实际上等于应用类别的(*>)
方法。这可以实现方法级联的功能风格。
所以在Haskell术语中,您想要此:
passAround :: Monad m => (a -> m b) -> a -> m a
passAround f x = do
f x
return x
将类型签名读取为" passAround
采用函数f :: a -> m b
,其结果为a nonadic Action (即,可能具有可以按明确定义的顺序进行测序的副作用的东西,因此具有任意结果类型b
的Monad m
约束)和通过此功能的值a
。它通过结果型a
产生单调的动作。"
要查看这可能对应的"功能编程构造",让我们首先展开此语法。在Haskell中,do
测序符号只是 monadic Compinators 的句法糖,即,
do
foo
bar
是foo >> bar
的糖。(这确实有点微不足道,整个事情实际上只有在您也将本地结果绑定到变量时才会变得有趣。)
so,
passAround f x = f x >> return x
>>
本身是通用的monadic链操作员的速记,即
passAround f x = f x >>= const (return x)
或
passAround f x = f x >>= y -> return x
(Backslash表示Lambda函数,在JavaScript中,它将读取f(x) >>= (y)=>return x
。)
现在,您真正想要的一切是,链接多个动作。在JavaScript中,您会写g(passAround(f, x))
,在Haskell中,这不仅是一个函数参数,因为它仍然是一个单调的动作,因此您想要另一个Monadic链接操作员:g =<< passAround f x
或
passAround f x >>= g
如果我们在这里扩展passAround
,我们得到
(f x >>= y -> return x) >>= g
现在,在这里我们可以应用单一法律,即协会法,给我们
f x >>= (y -> return x >>= g)
现在左派法律
f x >>= (y -> g x)
iow,整个构图崩溃至f x >> g x
,也可以写成
do
f x
g x
...是, duh 。这一切呢?好吧,好事是,我们可以使用 Monad Transformer 在这个单核绑带上抽象。在Haskell中,称为ReaderT
。如果您知道f
和g
都使用变量x
,您将要做什么,您可以交换
f :: a -> m b
g :: a -> m c
f' :: ReaderT a m b
f' = ReaderT f
g' :: ReaderT a m c
g' = ReaderT g
ReaderT
值构造函数在概念上对应于您的passAround
函数。
请注意,ReaderT a m c
具有 (ReaderT a m) c
的形式,或者忽略详细信息, m' c
,其中 m'
又是又是monad !并且,使用该单子的do
语法,您可以简单地写
runReaderT (do
f'
g'
) x
在JavaScript外观中,理论上,例如
runReaderT (() => {
f';
g';
}, x)
不幸的是,您实际上不能以这种方式写入它,因为与Haskell不同,命令式语言总是使用相同的单调来对其操作进行排序(这大致对应于Haskell的IO
Monad)。顺便说一句,这是的标准描述之一 :这是一个超载的半洛龙操作员。
您的 can 当然是在JavaScript语言的功能部分中实现单调变压器。我只是不确定是否值得付出努力。