函数式编程是否具有此逻辑的标准结构?
const passAround = (f) => (x) => {
f(x);
return x;
};
这使我能够编写具有副作用且没有返回值的函数,例如console.log
。它不像任务,因为我不想表示副作用的状态。
答案 0 :(得分:5)
如果你在谈论纯函数式编程,那么你需要挑战这个起点:
具有副作用且没有返回值的函数
在函数式编程中,没有这样的东西。每个函数都被定义为某些输入转换为某个输出。
所以显而易见的问题是,你如何在没有副作用的情况下代表console.log
?要回答,我们需要在您的问题中挑战另一个假设:
我不想表示副作用的状态
这正是函数式编程代表问题的方式:将输入和输出视为"世界的状态"。换句话说,给定函数之前的世界状态,返回函数之后的世界状态。在这种情况下,您将表示控制台的状态:给定具有x行输出的控制台,返回具有x + 1行输出的控制台。粗略地说,你可以这样写:
(x, console) => { return [x, console.withExtraLine(x)]; }
通常用于表示此功能的更强大的机制称为" monad" - 一种特殊的对象,它包含一系列步骤以及一些额外的含义。在IO
monad的情况下,每个步骤都包含一个将改变世界状态的动作。 (I / O只是monad概念的许多有用应用之一。)
您将这些步骤编写为仅了解"未包装的"该状态的某些部分的值(例如,最终来自用户输入的参数),并且monad处理实际执行功能程序领域之外的的混乱细节。因此,不要将输入和输出视为"世界状态",而是将输入视为"计算链",并将输出视为" a稍微长一点的计算链"。
有很多介绍要比我能提供的要好得多,只需要搜索" monad"或"函数式编程io"。
另请参阅,this answer,this question,以及可能在"相关"当您查看此问题时,会自动生成侧栏。
答案 1 :(得分:4)
SKI combinator calculus可能会让您感兴趣。让我们假设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);

无论如何,我提出SKI组合子微积分的原因是因为我想向你介绍Applicative Functors的概念。特别是,Reader
应用函子是SKI组合子微积分的equivalent。 S
组合子等同于Reader
的{{3}}方法,而K
组合子等同于Reader
的{{3}}方法。
在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);

但是等等,还有更多,你可以用applicative functor做。每个applicative functor也是一个仿函数。这意味着应用仿函数还必须具有ap
方法。对于Reader
,map
方法仅为pure
。它等同于map
组合子。使用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
函数实际上等同于Applicative类的function composition方法。这样可以实现B
的功能样式。
答案 2 :(得分:1)
所以在Haskell术语中,你想要这个:
passAround :: Monad m => (a -> m b) -> a -> m a
passAround f x = do
f x
return x
读取类型签名为“passAround
采用函数f :: a -> m b
,其结果为 monadic动作(即可能具有可排序的副作用的东西以明确定义的顺序,因此Monad m
约束)具有任意结果类型b
,并使用值a
来传递此函数。它产生一个结果类型为a
的monadic动作。“
要了解这可能对应的“函数式编程结构”,让我们首先展开这种语法。在Haskell中,do
测序符号只是 monadic组合器的语法糖,即
do
foo
bar
是foo >> bar
的糖。 (这实际上有点微不足道,当你将局部结果绑定到变量时,整个事情真的只会变得有趣。)
所以,
passAround f x = f x >> return x
>>
本身是一般monadic-chaining运算符的简写,即
passAround f x = f x >>= const (return x)
或
passAround f x = f x >>= \y -> return x
(反斜杠表示一个lambda函数,在JavaScript中它会读f(x) >>= (y)=>return x
。)
现在,您真正想要的是链接多个动作。在Javascript中你会写g(passAround(f, x))
,在Haskell中这不仅仅是一个函数参数,因为它仍然是一个monadic动作,所以你想要另一个monadic链接运算符:g =<< passAround f x
或
passAround f x >>= g
如果我们在这里展开passAround
,我们就会
(f x >>= \y -> return x) >>= g
现在,我们可以在这里应用monad laws,即关联法,给我们
f x >>= (\y -> return x >>= g)
现在是左单位法
f x >>= (\y -> g x)
IOW,整个作品崩溃到f x >> g x
,也可以写成
do
f x
g x
......有点像, duh 。这一切是什么?嗯,好消息是我们可以使用 monad转换器来抽象这个monad-rewrapping。在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 !并且,使用该monad的do
语法,您只需编写
runReaderT (do
f'
g'
) x
在JavaScript中看起来,理论上,如
runReaderT (() => {
f';
g';
}, x)
不幸的是你实际上不能用这种方式编写它,因为与Haskell不同,命令式语言总是使用相同的monad对它们的操作进行排序(大致对应于Haskell的IO
monad)。顺便说一下,这是 monad是的标准描述之一:它是overloaded semicolon operator。
当然可以做的是在JavaScript语言的功能部分中对动态类型实现monad转换。我只是不确定是否值得付出努力。