Elixir-style pipelines in Ruby

by gregnavison 12/10/2022, 2:47 PMwith 16 comments

by bwilliamson 12/10/2022, 5:00 PM

The beauty (and horror) of Ruby is that you can do almost anything with it. I think this is a really interesting and clever use of the "can do anything" aspect of Ruby, although I think I'd prefer not to run into it in a production app.

Still, it's really cool to see how far we can push/mold the language to accomplish different tasks and patterns.

by joeman1000on 12/10/2022, 10:16 PM

I've had a look at threading/piping operators in a few languages (list below). I'd say that Racket has the best one I've used. I love that you can specify a '_' for the hole which the result from the previous operation will fill. Julia's threading macro is surprisingly brittle, only letting you chain single-argument functions unless you want to use anonymous functions with one bound variable and the rest free.

+ Haskell :: https://www.schoolofhaskell.com/user/Gabriel439/Pipes%20tuto...

+ Racket :: https://docs.racket-lang.org/threading/index.html

+ Clojure :: https://clojure.org/guides/threading_macros

+ Julia :: https://syl1.gitbook.io/julia-language-a-concise-tutorial/us...

+ R :: https://r4ds.had.co.nz/pipes.html

by bonquesha99on 12/10/2022, 8:56 PM

Check out this proof of concept gem to perform pipe operations in Ruby using block expressions:

https://github.com/lendinghome/pipe_operator#-pipe_operator

  "https://api.github.com/repos/ruby/ruby".pipe do
    URI.parse
    Net::HTTP.get
    JSON.parse.fetch("stargazers_count")
    yield_self { |n| "Ruby has #{n} stars" }
    Kernel.puts
  end
  #=> Ruby has 15120 stars

by quechimbaon 12/10/2022, 9:32 PM

I like using .then for chaining stuff, like this:

    sig { params(obj: T.untyped).void }
    def write(obj)
      obj
        .then { Array(_1) }
        .then { @wrapper.pack(_1) }
        .then { @deflate.deflate(_1, Zlib::SYNC_FLUSH) }
        .then { @body.write(_1) }
    end

by ilyashon 12/11/2022, 4:56 AM

While not as thorough solution as mentioned here in comments, UFCS goes a long way in this direction. In Next Generation Shell, I've designed the methods so that the first parameter is something that is likely to come from "the pipeline". Hence mylist.filter(...).map(...) just work. Combined with multiple dispatch and the fact that methods don't belong to a particular type/class, it allows creating user-defined methods with same convention to work with any existing and new types/classes.

UFCS - https://en.m.wikipedia.org/wiki/Uniform_Function_Call_Syntax

by zelphirkalton 12/10/2022, 5:59 PM

Do the steps of the pipeline implementation in Ruby run concurrently?

I once did an Elixir course and really liked the pipelines. I continued implementing pipelines with a Scheme macro, but not concurrently.