代数效应在FP中意味着什么?

时间:2018-04-03 09:33:10

标签: javascript functional-programming

参考:

我搜索了很多链接,但似乎没有人能够具体解释它。任何人都可以提供一些代码(使用javaScript)来解释它吗?

4 个答案:

答案 0 :(得分:6)

什么是代数效应?

TL; DR:简而言之,代数效应是一种例外机制,可让throw ing函数继续其操作。

尝试将代数效应视为某种try / catch机制,其中catch处理程序不仅“处理异常”,而且能够向引发异常的函数提供一些输入。然后,将catch处理程序的输入用于throwing函数,该函数将继续运行,就像没有异常一样。

一些示例伪代码:

让我们考虑一个需要一些数据来执行其逻辑的函数:

function throwingFunction() {
    // we need some data, let's check if the data is here
    if (data == null) {
        data = throw "we need the data"
    }
    // do something with the data
}

然后我们有调用此功能的代码:

function handlingFunction() {
    try {
        throwingFunction();
    } catch ("we need the data") {
        provide getData();
    }
}

如您所见,throw语句是对catch处理程序提供的数据求值的表达式(我在这里使用了关键字provide,在任何编程中都不存在afaik今天的语言。

为什么如此重要?

代数效应是一个非常笼统的基本概念。可以从以下事实中看出这一点:许多现有概念可以用代数效应表示。

try / catch

如果我们喜欢的编程语言中有代数效应但没有异常,则只需在provide处理程序中省略catch关键字,瞧瞧,我们就会有一个异常机制。

换句话说,如果我们有代数效应,则不需要任何例外。

async / await

再次查看上面的伪代码。假设必须通过网络加载所需的数据。如果数据还没有,我们通常会返回一个Promise并使用async / await来处理它。这意味着我们的函数成为异步函数,只能从异步函数中调用。但是,代数效应也可以实现这种行为:

function handlingFunction() {
    try {
        throwingFunction();
    } catch ("we need the data") {
        fetch('data.source')
            .then(data => provide data);
    }
}

谁说provide关键字必须立即使用?

换句话说,如果我们在async / await之前有代数效应,则无需将语言与它们弄乱。此外,代数效果不会渲染我们的函数colorful-从语言的角度来看,我们的函数不会变得异步。

面向方面的编程

假设我们希望在代码中包含一些日志语句,但是我们尚不知道它将是哪个日志库。我们只想要一些常规的日志语句(我在这里用关键字throw替换了关键字effect,以使其更具可读性-请注意,effect不是任何语言的关键字知道):

function myFunctionDeepDownTheCallstack() {
    effect "info" "myFunctionDeepDownTheCallstack begins"

    // do some stuff

    if (warningCondition) {
        effect "warn" "myFunctionDeepDownTheCallstack has a warningCondition"
    }

    // do some more stuff

    effect "info" "myFunctionDeepDownTheCallstack exits"
}

然后我们可以在几行中连接任何一个日志框架:

try {
    doAllTheStuff();
}
catch ("info" with message) {
    log.Info(message);
}
catch ("warn" with message) {
    log.Warn(message);
}

这样,日志语句和实际进行日志记录的代码就会分开。

如您所见,throw关键字在非常通用的代数效果的上下文中并不真正适合。更合适的关键字是effect(此处使用)或perform

更多示例

使用代数效应可以轻松实现其他现有语言或库结构:

  • 具有yield的迭代器。具有代数效应的语言不需要yield语句。
  • React Hooks(这是库级别的结构示例-这里的其他示例是语言结构)。

答案 1 :(得分:3)

据我所知,代数效应目前是一个学术/实验概念,可让您使用类似于{{的机制来更改某些称为“效应”的计算元素(如函数调用,打印语句等)。 1}}

我能用JavaScript之类的语言想到的最简单的示例是修改throw catch中的输出消息。假设出于某种原因,您想在所有console.log语句之前添加“ Debug Message:”。这在JavaScript中会很麻烦。基本上,您需要在每个console.log上调用一个函数,如下所示:

console.log

现在,如果您有许多function logTransform(msg) { return "Debug Message: " + msg; } console.log(logTransform("Hello world")); 语句,则要在日志记录中引入更改,则每个语句都需要更改。现在,代数效应的概念将使您能够处理console.log在系统上的“效应”。可以像console.log那样在调用之前抛出一个异常,然后这个异常(效果)冒泡并且可以处理。唯一的区别是:如果未处理,执行将像没有任何反应一样继续进行。这没有让您做的是在任意范围(全局或局部)中操纵console.log的行为,而无需操纵对console.log的实际调用。可能看起来像这样:

console.log

请注意,这不是JavaScript,我只是在编写语法。由于代数效应是一种实验性的构造,因此我所知的任何主要编程语言均未提供本机支持(但是有几种实验性语言,例如eff https://www.eff-lang.org/learn/)。希望您能大致了解我的伪造代码是如何工作的。在try catch块中,可以处理 try { console.log("Hello world"); } catch effect console.log(continuation, msg) { msg = "Debug message: " + msg; continuation(); } 可能引发的效果。 Continuation是一种类似令牌的构造,需要控制正常工作流程何时继续。不需要这样的东西,但是它可以让您在console.log之前和之后进行操作(例如,您可以在每个console.log之后添加一条额外的日志消息)

总而言之,代数效应是一个有趣的概念,可以帮助解决许多现实世界中的编码问题,但是如果方法的行为与预期突然不同,它也会引入某些陷阱。如果您现在想在JavaScript中使用代数效应,则必须自己为其编写框架,并且您可能始终无法将代数效应应用于诸如console.log之类的核心函数。基本上,您现在所能做的就是以抽象的角度探索该概念并思考它或学习一种实验语言。我认为这也是许多介绍性论文如此抽象的原因。

答案 2 :(得分:2)

没有范畴论的基础,很难获得扎实的代数效应理论理解,因此我将尝试以通俗易懂的方式解释其用法,可能会牺牲一些准确性。

计算效果是包括对其环境进行更改的任何计算。例如,总磁盘容量,网络连接之类的因素是外部影响,它们在诸如读/写文件或访问数据库之类的操作中发挥作用。函数产生的任何东西,除了它所计算的值之外,都是计算结果。从该功能的角度来看,甚至另一个访问与该功能相同的内存的功能也可以视为一种效果。

这是理论上的定义。实际上,将效果视为子表达式处理程序中全局资源的中央控件之间的任何交互都是有用的。有时,本地表达式可能需要在执行时将消息以及足够的信息发送给中央控件,以便一旦中央控件完成,它就可以恢复暂停的执行。

我们为什么要这样做?因为有时大型库的抽象链很长,可能会变得凌乱。使用“代数效应”,我们可以通过一种捷径在抽象之间传递事物,而无需遍历整个链。

作为一个实用的JavaScript示例,让我们来看一下像ReactJS这样的UI库。这个想法是,UI可以写为简单的数据投影。

例如,这将是按钮的表示。

function Button(name) {
  return { buttonLabel: name, textColor: 'black' };
}

'John Smith' -> { buttonLabel: 'John Smith', textColor: 'black' }

使用这种格式,我们可以创建一长串可组合的抽象。像这样

function Button(name) {
  return { buttonLabel: name, textColor: 'black' };
}

function UsernameButton(user) {
  return {
    backgroundColor: 'blue',
    childContent: [
      Button(user.name)
    ]
  }
}

function UserList(users){
  return users.map(eachUser => {
    button: UsernameButton(eachUser.name),
    listStyle: 'ordered'
  })
}

function App(appUsers) {
  return {
    pageTheme: redTheme,
    userList: UserList(appUsers)
  }
}

此示例具有四个组成在一起的抽象层。

App-> UserList-> UsernameButton-> Button

现在,让我们假设对于这些按钮中的任何一个,我都需要继承其运行的任何计算机的颜色主题。假设手机的文字是红色,而笔记本电脑的文字是蓝色。

主题数据位于第一个抽象(App)中。它需要在最后一个抽象(按钮)中实现。

烦人的方法是将主题数据从App传递到Button,并在此过程中修改每个抽象。

应用程序将主题数据传递到UserList UserList将其传递给UserButton UserButton将其传递给Button

显而易见,在具有数百个抽象层的大型库中,这是一个巨大的痛苦。

一种可能的解决方案是通过特定的效果处理程序传递效果,并在需要时让它继续

function PageThemeRequest() {
  return THEME_EFFECT;
}

function App(appUsers) {
  const themeHandler = raise new PageThemeRequest(continuation);
  return {
    pageTheme: themeHandler,
    userList: UserList(appUsers)
  }
}

// ...Abstractions in between...

function Button(name) {
  try {
    return { buttonLabel: name, textColor: 'black' };
  } catch PageThemeRequest -> [, continuation] {
    continuation();
  }
}

这种效果处理,链中的一个抽象可以暂停其工作(主题实现),将必要的数据发送到中央控件(可以访问外部主题的App),并传递所需的数据对于延续,是代数处理效果的极其简单的示例。

答案 3 :(得分:0)

您可以签出algebraic-effects。它是一个库,使用生成器函数(包括多个延续)在javascript中实现了许多代数效应的概念。从try-catch(异常效应)和生成器函数的角度理解代数效应要容易得多。