Customizable/query/computation/monad pipes (railway-oriented programming)
follow-on proposal
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 条评论