用于组合身份和副作用的函数式编程结构

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。如果你知道 fg 都使用变量 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组合子演算,是想给大家介绍一下ReaderApplicative 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 中的函数定义 appure 如下:

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 .