Note-53444-58

Token ID: 2

ERC-721 1 Transfers

Metadata

{
  "title": "CPS系列 1: CPS(延续传递) 对正常人来说有什么用?",
  "tags": [
    "post",
    "CPS",
    "plt",
    "编程语言"
  ],
  "summary": "CPS(Continuation Passing Style)编程是一种编程风格,它的主要思想是将程序的控制流程显示地传递给下一个函数,而不是通过函数调用栈来控制。 在CPS编程中,每个函数都需要一个额外的参数,这个参数被称为 \"continuation\" (或者我们一般叫他 k…",
  "sources": [
    "xlog"
  ],
  "external_urls": [
    "https://lemonhx.xlog.app/CPS-xi-lie-1-CPS-yan-xu-chuan-di--dui-zheng-chang-ren-lai-shuo-you-shi-mo-yong-"
  ],
  "date_published": "2023-04-13T19:24:22.350Z",
  "content": "\nCPS(Continuation Passing Style)编程是一种编程风格,它的主要思想是将程序的控制流程显示地传递给下一个函数,而不是通过函数调用栈来控制。\n\n在CPS编程中,每个函数都需要一个额外的参数,这个参数被称为 \"continuation\" (或者我们一般叫他 `k` ),它是一个函数,表示程序执行完当前函数后要继续执行的代码。\n在函数执行完之后,它会将结果传递给continuation函数,从而控制程序的执行流程。这样,函数调用就变成了一个连续的函数调用链,每个函数都负责将结果传递给下一个函数,从而实现了控制流的显示传递。\n\n我和主流作者不同的是我会用正常人看的编程语言来讲解CPS,而不是用一些奇怪的语言,比如 `Scheme` 。\n```js\n// 普通函数\nfunction add(a, b) {\n  return a + b;\n}\n\n// CPS函数\nfunction _add(a, b, k) {\n  return k(a + b);\n}\n```\n\n有人会问他们有什么显著区别吗?\n有!而且很大,举个例子\n\n```js\nfunction whatever() {\n    const a = add(1, 2);\n    const b = add(3, 4);\n    return a + b;\n}\n```\n\n正常人写出这种代码是非常显而易见的\n\n```js\nfunction _whatever() {\n    return _add(1, 2, \n        (a) => _add(3, 4, \n            (b) => \n                a + b));\n}\n```\n\n这么写代码有什么优点呢?\n我可以毫不客气的讲,如果你这么写代码我是你上司我第一个刀了你。\n\n不过假如说你这么写的话就好看的多了\n```js\nfunction _whatever2() {\n    // 手动柯里化一下\n    const call = (f) => (...args) => (k) => f(...args, k);\n    const add12 = call(_add)(1, 2);\n    const add34 = call(_add)(3, 4);\n    return add12((a) => add34((b) => a + b));\n}\n```\n\n你会发现这样你把一个连续的函数拆成了一个个的函数,这样你就可以在任何地方调用这个函数了,而不是只能在函数的最后调用。\n\n这样做的好处就是你可以任意的传递函数的控制流,而不是只能在函数的最后调用。\n所以这个函数可以被拆分成多个函数,这样就可以实现函数的复用。\n\n```js\nconst call = (f) => (...args) => (k) => f(...args, k);\nconst add12 = call(_add)(1, 2);\nconst add34 = call(_add)(3, 4);\n\nfunction _whatever3(add12, add34) {\n    return add12((a) => add34((b) => a + b));\n}\n```\n\n假如有个逻辑改变,你只需要改变一个函数就可以了,而不是整个函数\n```js\n_whatever3(add12, add34);\n_whatever3((k)=>k(0), add34);\n// 思考一下下面的发生了什么?\n_whatever3((k)=>0, add34);\n```\n\n很简单,`k` 作为这个函数的延续,假如你并没有调用 `k` 就返回了一个值,那么这个值就会被当做结果返回\n\n这赋予了你更多的可组合性\n\n## 那这玩意儿有什么用呢?我之前代码写的好好的为啥学这个?\n\n比如说有个错误处理的例子非常好的说明了这个问题\n\n```js\n// 一会儿手动调节下看下效果\nvar fail = false;\n\nfunction maybe_error_function_(ok_do, error_do, k) {\n    if (!fail) {\n        ok_do(k);\n    } else {\n        error_do(()=>{});\n    }\n}\n\nfunction main() {\n    const ok_do = (k) => {\n        console.log(\"ok\");\n        k();\n    };\n    const error_do = (k) => {\n        console.log(\"error\");\n        k();\n    };\n    const before = call(_add)(1, 2);\n    const maybe_err = call(maybe_error_function_)(ok_do, error_do);\n    const after = call(_add)(3, 4);\n    return before((befor_result) => maybe_err((maybe_err_result) => after((after_result) => {\n        console.log(befor_result + after_result);\n        console.log(\"done\");\n    })));\n}\n```\n\n你会发现,诶!卧槽,怎么错误处理的逻辑写起来跟正常的逻辑一样了???\n\n我现在可以告诉你,这个就是 Rust 语言的 `?` 运算符的实现原理\n\n他就是在编译器层面把这个函数拆成了一个个的函数,然后把这些函数组合起来,然后再内联掉,这样就实现了 `?` 运算符的功能\n\n> 下一章我会讲什么叫 **限界延续(Delimited Continuation)** 可能会引入过多思考,请提前吃点核桃",
  "attributes": [
    {
      "value": "CPS-xi-lie-1-CPS-yan-xu-chuan-di--dui-zheng-chang-ren-lai-shuo-you-shi-mo-yong-",
      "trait_type": "xlog_slug"
    }
  ]
}