功能编程构造用于组成身份和副作用

时间:2017-09-08 12:27:40

标签: javascript functional-programming side-effects

函数式编程是否具有此逻辑的标准结构?

const passAround = (f) => (x) => {
  f(x);

  return x;
};

这使我能够编写具有副作用且没有返回值的函数,例如console.log。它不像任务,因为我不想表示副作用的状态。

3 个答案:

答案 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 answerthis 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组合子微积分的equivalentS组合子等同于Reader的{​​{3}}方法,而K组合子等同于Reader的{​​{3}}方法。

在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);




但是等等,还有更多,你可以用applicative functor做。每个applicative functor也是一个仿函数。这意味着应用仿函数还必须具有ap方法。对于Readermap方法仅为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。如果您知道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)。顺便说一下,这是 monad是的标准描述之一:它是overloaded semicolon operator

当然可以做的是在JavaScript语言的功能部分中对动态类型实现monad转换。我只是不确定是否值得付出努力。