Javascript 代数效应在FP中意味着什么?
参考:Javascript 代数效应在FP中意味着什么?,javascript,functional-programming,Javascript,Functional Programming,参考: 我搜索了很多链接,但似乎没有人能具体解释。有人能给我一些代码(使用javaScript)来解释它吗?据我所知,代数效应目前是一个学术/实验概念,它允许你通过使用类似于抛出捕捉 在JavaScript这样的语言中,我能想到的最简单的例子是修改console.log中的输出消息。假设出于任何原因,您希望在所有控制台.log语句前面添加“Debug Message:”。这在JavaScript中是个麻烦。基本上,您需要在每个控制台上调用一个函数 function logTransfo
我搜索了很多链接,但似乎没有人能具体解释。有人能给我一些代码(使用javaScript)来解释它吗?据我所知,代数效应目前是一个学术/实验概念,它允许你通过使用类似于
抛出捕捉
在JavaScript这样的语言中,我能想到的最简单的例子是修改console.log
中的输出消息。假设出于任何原因,您希望在所有控制台.log
语句前面添加“Debug Message:”。这在JavaScript中是个麻烦。基本上,您需要在每个控制台上调用一个函数
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)。我希望您大致了解我编写的代码是如何工作的。在try-catch块中,可以处理console.log
可能引发的效果。Continuation是一个类似令牌的构造,它需要控制正常工作流何时继续。没有必要有这样的东西,但它允许您在console.log
之前和之后进行操作(例如,您可以在每个console.log之后添加额外的日志消息)
总而言之,代数效应是一个有趣的概念,它有助于解决编码中的许多实际问题,但如果方法突然表现出与预期不同的行为,它也会引入某些陷阱。如果您现在想在JavaScript中使用代数效果,您必须自己为其编写一个框架,而且您可能无法将代数效果应用于核心函数,例如console.log
。基本上,你现在所能做的就是在一个抽象的尺度上探索这个概念,思考它或者学习一种实验性语言。我认为这也是许多介绍性论文如此抽象的原因。没有范畴理论的基础,很难对代数效应有一个坚实的理论理解,因此我将尝试用外行术语解释它的用法,可能会牺牲一些准确性
计算效果是指包括环境改变的任何计算。例如,总磁盘容量、网络连接等都是外部影响,在读/写文件或访问数据库等操作中发挥作用。函数产生的任何东西,除了它计算的值之外,都是一种计算效果。从该函数的角度来看,即使另一个函数访问与该函数相同的内存,也可以认为是一种影响
这是理论上的定义。实际上,将效果视为子表达式和在程序中处理全局资源的中央控件之间的任何交互都是有用的。有时,本地表达式可能需要在执行时向中央控件发送消息以及足够的信息,以便一旦中央控件完成,它就可以恢复暂停的执行
我们为什么要这样做?因为有时大型库有很长的抽象链,这可能会变得混乱。使用“代数效应”,给了我们一种在抽象之间传递事物的捷径,而不必穿过整个链条
作为一个实用的JavaScript示例,让我们使用一个像ReAcjs那样的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将其传递给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();
}
}
这种类型的效果处理,其中链中的一个抽象可以暂停其所做的事情(主题实现),将必要的数据发送到中央控制(应用程序,可以访问外部主题),并传递继续所需的数据,是一个极其简单的示例
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();
}
}
function handlingFunction() {
try {
throwingFunction();
} catch ("we need the data") {
fetch('data.source')
.then(data => provide data);
}
}
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);
}
function* myRoutineWithEffects() {
// prepare data load
let data = yield put({ /* ... description of data to load */ });
// use the data
}