proposal-pipeline-operator icon indicating copy to clipboard operation
proposal-pipeline-operator copied to clipboard

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

Open js-choi opened this issue 6 months ago • 0 comments

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:
    • Optional chaining (#159 and #198).
    • 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.
      • Each consecutive inner callback is a step in the pipeline.
      • The parameter of each consecutive inner callback is the argument to its following steps.
  • 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 and its other 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. Even the name “railway-oriented programming” sounds like a more complicated version of “pipeline-oriented programming”.
  • Years ago, I made an old, abandoned ES “context blocks” proposal for blocks of based on IIFEs.
    • ES context blocks were inspired by F# computation expressions and other languages’ monadic 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 and 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.
    • They would functionally act quite similar to the idea in #274.
  • 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.
    • Context pipes could create complex query scaffolding at each step of the pipeline.
    • Context pipes would cover optional pipes (#159 and #198), which are just context pipes using a maybe monad.
    • They would also support custom contexts like LINQ query building.
  • But, this is for a far-future add-on proposal.
    • These are just half-formed ideas right now.
    • The Committee has become very generally chilly towards any new syntax.
    • This has become especially uncertain with the recent JS0/JSSugar proposal.
    • 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.

js-choi avatar Apr 09 '25 05:04 js-choi