功能编程结构,用于构成身份和副作用



功能编程是否具有此逻辑的标准构造?

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组合器等效于Readerap方法,K组合器等效于Readerpure方法。

在JavaScript中,相当于ReaderFunction。因此,我们可以在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方法。对于Readermap方法只是功能组成。它等效于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 (即,可能具有可以按明确定义的顺序进行测序的副作用的东西,因此具有任意结果类型bMonad 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。如果您知道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 !并且,使用该单子的do语法,您可以简单地写

 runReaderT (do
     f'
     g'
  ) x

在JavaScript外观中,理论上,例如

 runReaderT (() => {
      f';
      g';
    }, x)

不幸的是,您实际上不能以这种方式写入它,因为与Haskell不同,命令式语言总是使用相同的单调来对其操作进行排序(这大致对应于Haskell的IO Monad)。顺便说一句,这是的标准描述之一 :这是一个超载的半洛龙操作员。

您的 can 当然是在JavaScript语言的功能部分中实现单调变压器。我只是不确定是否值得付出努力。

相关内容

最新更新