Pet*_*ter 5

就我所了解的主题而言,代数效应目前是一个学术/实验性概念,可让您使用类似的机制来更改某些称为“效果”的计算元素(例如函数调用,打印语句等)。 throw catch

我能想到的最简单的示例是使用JavaScript之类的语言,是在let say中修改输出消息console.log。假设console.log出于任何原因,您都想在所有语句之前添加“调试消息:” 。这在JavaScript中会很麻烦。基本上,您需要console.log像这样调用每个函数:

function logTransform(msg) { return "Debug Message: " + msg; }
console.log(logTransform("Hello world"));
Run Code Online (Sandbox Code Playgroud)

现在,如果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();
 }
Run Code Online (Sandbox Code Playgroud)

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

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


has*_*ram 5

如果没有范畴论的基础,很难对代数效应有扎实的理论理解,所以我将尝试用外行术语解释它的用法,可能会牺牲一些准确性。

一个计算效果的是,包括它的环境的改变任何计算。例如,总磁盘容量、网络连接等都是外部效应,它们在读/写文件或访问数据库等操作中发挥作用。一个函数产生的任何东西,除了它计算的值,都是计算效果。从该函数的角度来看,即使另一个函数访问与该函数相同的内存,也可以视为效果。

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

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

作为一个实际的 JavaScript 示例,让我们以 ReactJS 之类的 UI 库为例。这个想法是可以将 UI 编写为数据的简单投影。

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

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

'John Smith' -> { buttonLabel: 'John Smith', textColor: 'black' }
Run Code Online (Sandbox Code Playgroud)

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

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)
  }
}
Run Code Online (Sandbox Code Playgroud)

这个例子有四层抽象组合在一起。

应用程序 -> 用户列表 -> 用户名按钮 -> 按钮

现在,让我们假设对于这些按钮中的任何一个,我需要继承它运行的任何机器的颜色主题。比如说,手机有红色文字,而笔记本电脑有蓝色文字。

主题数据在第一个抽象(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();
  }
}
Run Code Online (Sandbox Code Playgroud)

这种类型的效果处理,链中的一个抽象可以暂停它正在做的事情(主题实现),将必要的数据发送到中央控制(可以访问外部主题的应用程序),并传递继续所需的数据,是代数处理效果的一个极其简单的例子。


che*_*ica 5

什么是代数效应?

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

尝试将代数效果视为某种排序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
}
Run Code Online (Sandbox Code Playgroud)

然后我们有调用此函数的代码:

function handlingFunction() {
    try {
        throwingFunction();
    } catch ("we need the data") {
        provide getData();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为什么这很重要?

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

try/catch

如果我们最喜欢的编程语言中有代数效应但没有异常,则只需providecatch处理程序中省略关键字,否则,我们将具有异常机制。

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

async/await

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

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

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

换句话说,如果我们在async/ 之前有代数效应await,则无需将语言与它们混杂在一起。此外,代数效果不会使我们的函数丰富多彩 -从语言的角度来看,我们的函数不会变得异步。

面向方面的编程

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

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

    // do some stuff

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

    // do some more stuff

    effect "info" "myFunctionDeepDownTheCallstack begins"
}
Run Code Online (Sandbox Code Playgroud)

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

try {
    doAllTheStuff();
}
catch ("info" with message) {
    log.Info(message);
}
catch ("warn" with message) {
    log.Warn(message);
}
Run Code Online (Sandbox Code Playgroud)

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

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

更多例子

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

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