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

问题描述 投票:22回答:3

参考:

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

javascript functional-programming
3个回答
3
投票

就我理解这个主题而言,代数效应目前是一个学术/实验概念,它允许你通过使用类似于throw catch的机制来改变称为“效果”的某些计算元素(如函数调用,打印语句等)。

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

function logTransform(msg) { return "Debug Message: " + msg; }
console.log(logTransform("Hello world"));

现在,如果您有许多console.log语句,如果您想引入日志记录中的更改,则需要更改每个语句。现在,代数效应的概念将允许您处理console.log对系统的“影响”。可以想象它像console.log在调用之前抛出一个异常,这个异常(效果)冒泡并可以处理。唯一的区别是:如果未处理,执行将继续,就像什么都没发生一样。不能让你做的是在任意范围(全局或本地)中操纵console.log的行为而不操纵对console.log的实际调用。看起来像这样:

 try
 {
 console.log("Hello world");
 }
 catch effect console.log(continuation, msg)
 {
    msg = "Debug message: " + msg;
    continuation();
 }

请注意,这不是JavaScript,我只是在编写语法。由于代数效应是一种实验性构造,因此我所知道的任何主要编程语言都不支持它们(然而有几种实验语言,例如eff https://www.eff-lang.org/learn/)。我希望你能大致了解我的编写代码是如何工作的。在try catch块中,可以处理console.log可能引发的效果。 Continuation是一种类似于令牌的构造,用于控制正常工作流何时应该继续。没有必要有这样的东西,但它允许你在console.log之前和之后进行操作(例如,你可以在每个console.log之后添加额外的日志消息)

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


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)中。它需要在最后的抽象(Button)中实现。

令人讨厌的方式是传递主题数据,从App到Button,修改沿途的每一个抽象。

App将主题数据传递给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),并传递继续所需的数据,是一个非常简单的代数处理效果的例子。


0
投票

你可以看看algebraic-effects。它是一个库,它使用生成器函数在javascript中实现了许多代数效果的概念,包括多个continuation。在try-catch(异常效果)和生成器函数方面理解代数效应要容易得多。

© www.soinside.com 2019 - 2024. All rights reserved.