ITADN

Customizable/query/computation/monad pipes (railway-oriented programming)

#312Openjs-choi 创建于 2025-04-09
follow-on proposal
J
js-choicommented
This is a spinoff of https://github.com/tc39/proposal-pipeline-operator/issues/311#issuecomment-2788293430 and #159. * In many contexts, such as query building, pipes oftentimes may need to do repetitive computations or queries. These contexts include: * Observables. * Database queries (e.g., SQL, LINQ). * Parser combinators. * Data sources like Facebook’s Haxl. * (I wrote this issue in a hurry, so the above use cases need specific code examples later, maybe from my old [context blocks][] proposal.) * Railway-oriented programming: * Many of these contexts share a common pattern. * Their computations generally run on a “happy path”. * But there is an alternative track that needs to be handled differently (such as expected operational exceptions). * This is sometimes called “railway-oriented programming” in F# and C#. * In JavaScript… * If the pipeline (or “railway”) is simple, with only one (synchronous) happy path and no special processing on each step, then simple assignment to temporary variables works (#311). * If there is an alternative non-happy path, any asynchrony is involved, or there is special processing on any state, then the pipeline cannot use simple variable assignment. * In that case, it must use nested callbacks / continuations / IIFEs (forming deeply nested [[pyramids of doom](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming))](pyramids of doom)). * Each consecutive inner callback is a step in the pipeline. * The parameter of each consecutive inner callback is the argument to its following steps. * With `|>` pipelines specifically, the parameter is the topic reference. * Haskell’s solution (as well as those of many other functional programming languages) includes functor applicators, monads, monoids, arrows, and so on. * F#’s solution: * F# has solved this using [query expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/query-expressions) and its other [computation expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions). * F# uses its query expressions to support LINQ, as well as LINQ-like syntax in other contexts. * In actuality, F# query/computation expressions are a clever way to implement monads (and applicator functors and monoids). * There is a deep relationship between Hack pipes and F#’s query expressions and computation expressions (and the LINQ queries they came from, and do-notation, and so on). * F# query/computation expressions flatten deeply nested callbacks (nested continuations or immediately invoked functions / IIFEs), taking their parameters and lining them up in a single linear pipeline. * This is indeed pretty similar to the goals of the ES pipe operator. Someone from F# has even called this method “railway-oriented programming”, which sure sounds like a more complicated version of “pipeline-oriented programming”. * Years ago, I made an old, abandoned ES “[context block][]” proposal for blocks of based on IIFEs. * ES context blocks were inspired by F# computation expressions and [other languages’ monadic do-notation](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=do-notation). * ES context blocks would allow people to use LINQ-like syntax generically in custom contexts, binding variables to each step of a custom sequence of computations or queries. * ES context blocks would encompass the inactive [`do` expressions](https://github.com/tc39/proposal-do-expressions) and [`async do` expressions](https://github.com/tc39/proposal-async-do-expressions). * ES context blocks would be extensible by the user for creating pipelines in other contexts, like observable/signal chaining, file processing, database queries, parsers, or anything else involving deeply nested callbacks / continuations. * The pipe operator could hypothetically be extended into a customizable “context pipe operator” that uses the same context-block monad infrastructure as [context blocks][]. * These “context” pipe operators that apply a monad or some other kind of custom “context” to a pipe, like `intOrNullValue |maybe> f(#)`, where `maybe` is a variable containing a “maybe monad” that would cause the pipeline to evaluate `f(#)` only if `intOrNullValue` is not nullish. * But, this is for a *far-future* add-on proposal. * The Committee has become very generally chilly towards *any* new syntax. * This has become especially uncertain with the recent [JS0/JSSugar proposal](https://github.com/tc39/notes/blob/main/meetings/2024-10/october-08.md#jssugarjs0). * There is no way that we can feasibly advance a more complicated pipe, let alone context pipes or context blocks. * That’s much of why I haven’t spent time on them. * If you want to discuss LINQ-like “customized” context pipelines here, or “context blocks”, or monads in general, feel free to discuss them here. [context blocks]: https://gist.github.com/js-choi/854ccbc34787c697ea1f8458d6a1d660
0 条评论