用于组合身份和副作用的函数式编程结构
Functional programming construct for composing identity and side effect
函数式编程是否有针对此逻辑的标准构造?
const passAround = (f) => (x) => {
f(x);
return x;
};
这使我能够编写具有副作用且没有 return 值的函数,例如 console.log
。它不像任务,因为我不想表示副作用的状态。
如果你说的是纯函数式编程,那么你需要挑战这个起点:
functions that have side effects and no return values
在函数式编程中,没有这样的东西。每个函数都被定义为将某些输入转换为某些输出。
所以显而易见的问题是,您如何表示 console.log
没有副作用?要回答这个问题,我们需要挑战您问题中的另一个假设:
I don't want to represent the state of the side effect
这正是函数式编程代表问题的方式:将您的输入和输出视为 "the state of the world"。换句话说,给定函数之前的世界状态,return 函数之后的世界状态。在这种情况下,您将表示控制台的状态:给定一个具有 x 行输出的控制台,return 一个具有 x+1 行输出的控制台。粗略地说,你可以这样写:
(x, console) => { return [x, console.withExtraLine(x)]; }
通常用于表示这一点的更强大的机制称为 "monad" - 一种特殊类型的对象,它包含一系列步骤以及一些额外的含义。在 IO
monad 的情况下,每个步骤都包含一个将改变世界状态的动作。 (I/O 只是 monad 概念的众多有用应用之一。)
你把步骤写成函数,它只知道状态某些部分的 "unwrapped" 值(例如,最终来自用户输入的参数),而 monad 处理实际执行的混乱细节功能程序领域之外。因此,与其将输入和输出视为 "the state of the world",不如将输入视为 "a chain of computations",将输出视为 "a slightly longer chain of computations".
对此有很多介绍,比我能给出的要好得多,只需搜索 "monad" 或 "functional programming io"。
另请参阅,this answer, this question,以及当您查看此问题时自动生成的 "Related" 边栏中的许多其他内容。
所以在 Haskell 术语中,您需要这样:
passAround :: Monad m => (a -> m b) -> a -> m a
passAround f x = do
f x
return x
将类型签名解读为“passAround
接受一个函数 f :: a -> m b
,其结果是一个 单子动作 (即,可能有副作用的东西可以按明确定义的顺序排序的效果,因此 Monad m
约束)具有任意结果类型 b
和值 a
以传递此函数。它产生一个结果类型为 a
的单子操作。”
要了解这可能对应于什么“函数式编程构造”,让我们首先展开此语法。在Haskell中,do
排序符号只是单子组合子的语法糖,即
do
foo
bar
是 foo >> bar
的糖。 (这真的有点微不足道,只有当您将本地结果绑定到变量时,整个事情才会变得有趣。)
所以,
passAround f x = f x >> return x
>>
本身是 shorthand 对于一般的 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 这不仅仅是一个函数参数,因为它仍然是一个单子动作,所以你需要另一个单子链接运算符: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 transformer 抽象这个 monad 重包装。在 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 的标准描述之一:它是一个 overloaded semicolon operator.
您可以当然可以做的是在JavaScript语言的功能部分中实现动态类型的monad转换器。我只是不确定是否值得付出努力。
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组合子演算,是想给大家介绍一下Reader
的Applicative Functors. In particular, the Reader
applicative functor is equivalent to the SKI combinator calculus. The S
combinator is equivalent to the ap
method of Reader
and the K
combinator is equivalent to the 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
method. For Reader
the map
method is just function composition. It's equivalent to the 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
函数实际上等同于 (*>)
method of the Applicative class. This enables a functional style of .
函数式编程是否有针对此逻辑的标准构造?
const passAround = (f) => (x) => {
f(x);
return x;
};
这使我能够编写具有副作用且没有 return 值的函数,例如 console.log
。它不像任务,因为我不想表示副作用的状态。
如果你说的是纯函数式编程,那么你需要挑战这个起点:
functions that have side effects and no return values
在函数式编程中,没有这样的东西。每个函数都被定义为将某些输入转换为某些输出。
所以显而易见的问题是,您如何表示 console.log
没有副作用?要回答这个问题,我们需要挑战您问题中的另一个假设:
I don't want to represent the state of the side effect
这正是函数式编程代表问题的方式:将您的输入和输出视为 "the state of the world"。换句话说,给定函数之前的世界状态,return 函数之后的世界状态。在这种情况下,您将表示控制台的状态:给定一个具有 x 行输出的控制台,return 一个具有 x+1 行输出的控制台。粗略地说,你可以这样写:
(x, console) => { return [x, console.withExtraLine(x)]; }
通常用于表示这一点的更强大的机制称为 "monad" - 一种特殊类型的对象,它包含一系列步骤以及一些额外的含义。在 IO
monad 的情况下,每个步骤都包含一个将改变世界状态的动作。 (I/O 只是 monad 概念的众多有用应用之一。)
你把步骤写成函数,它只知道状态某些部分的 "unwrapped" 值(例如,最终来自用户输入的参数),而 monad 处理实际执行的混乱细节功能程序领域之外。因此,与其将输入和输出视为 "the state of the world",不如将输入视为 "a chain of computations",将输出视为 "a slightly longer chain of computations".
对此有很多介绍,比我能给出的要好得多,只需搜索 "monad" 或 "functional programming io"。
另请参阅,this answer, this question,以及当您查看此问题时自动生成的 "Related" 边栏中的许多其他内容。
所以在 Haskell 术语中,您需要这样:
passAround :: Monad m => (a -> m b) -> a -> m a
passAround f x = do
f x
return x
将类型签名解读为“passAround
接受一个函数 f :: a -> m b
,其结果是一个 单子动作 (即,可能有副作用的东西可以按明确定义的顺序排序的效果,因此 Monad m
约束)具有任意结果类型 b
和值 a
以传递此函数。它产生一个结果类型为 a
的单子操作。”
要了解这可能对应于什么“函数式编程构造”,让我们首先展开此语法。在Haskell中,do
排序符号只是单子组合子的语法糖,即
do
foo
bar
是 foo >> bar
的糖。 (这真的有点微不足道,只有当您将本地结果绑定到变量时,整个事情才会变得有趣。)
所以,
passAround f x = f x >> return x
>>
本身是 shorthand 对于一般的 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 这不仅仅是一个函数参数,因为它仍然是一个单子动作,所以你需要另一个单子链接运算符: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 transformer 抽象这个 monad 重包装。在 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 的标准描述之一:它是一个 overloaded semicolon operator.
您可以当然可以做的是在JavaScript语言的功能部分中实现动态类型的monad转换器。我只是不确定是否值得付出努力。
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组合子演算,是想给大家介绍一下Reader
的Applicative Functors. In particular, the Reader
applicative functor is equivalent to the SKI combinator calculus. The S
combinator is equivalent to the ap
method of Reader
and the K
combinator is equivalent to the 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
method. For Reader
the map
method is just function composition. It's equivalent to the 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
函数实际上等同于 (*>)
method of the Applicative class. This enables a functional style of