{"id":21841129,"url":"https://github.com/zverok/not_a_pipe","last_synced_at":"2025-10-08T08:31:27.596Z","repository":{"id":263179636,"uuid":"889496851","full_name":"zverok/not_a_pipe","owner":"zverok","description":"An Elixir-like pipes for Ruby (oh no not again). Experiment/demo","archived":false,"fork":false,"pushed_at":"2024-11-16T18:29:40.000Z","size":26,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-11-27T21:34:07.344Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zverok.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-16T13:33:46.000Z","updated_at":"2024-11-25T13:16:36.000Z","dependencies_parsed_at":"2024-11-16T21:15:24.421Z","dependency_job_id":null,"html_url":"https://github.com/zverok/not_a_pipe","commit_stats":null,"previous_names":["zverok/not_a_pipe"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fnot_a_pipe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fnot_a_pipe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fnot_a_pipe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zverok%2Fnot_a_pipe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zverok","download_url":"https://codeload.github.com/zverok/not_a_pipe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235694190,"owners_count":19030772,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-27T21:29:10.179Z","updated_at":"2025-10-08T08:31:22.313Z","avatar_url":"https://github.com/zverok.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://en.wikipedia.org/wiki/The_Treachery_of_Images\"\u003e\u003cimg src=\"https://github.com/zverok/not_a_pipe/blob/main/img/une_pipe.jpg?raw=true\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# This is not a pipe\n\nThis is an experimental/demo Ruby implementation of Elixir-style pipes. It allows to write code like this:\n\n```ruby\nrequire 'not_a_pipe'\n\nextend NotAPipe\n\npipe def repos(username)\n  username \u003e\u003e\n    \"https://api.github.com/users/#{_}/repos\" \u003e\u003e\n    URI.open \u003e\u003e\n    _.read \u003e\u003e\n    JSON.parse(symbolize_names: true) \u003e\u003e\n    _.map { _1.dig(:full_name) }.first(10) \u003e\u003e\n    pp\nend\n```\n\nBasically:\n* `pipe` is a decorator to mark methods inside which `\u003e\u003e` works as “pipe operator”;\n* every step can reference `_` which would be a result of the previous step;\n* but it also can omit the reference and just specify a method to call; the result of the previous step would be substituted as the _first argument_ of the method.\n\n`not_a_pipe` works by _rewriting the AST_ and reevaluating the (rewritten) method code at the definition time and has no runtime penalty; thus achieving something akin to macros.\n\n**It is not intended to use in production codebase**, but rather as an approach investigation/demonstration.\n\nInspired by a [Python’s library](https://github.com/Jordan-Kowal/pipe-operator?tab=readme-ov-file#-elixir-like-implementation) that uses the similar approach, and a [recent discussion](https://bugs.ruby-lang.org/issues/20770#note-34) in Ruby’s bug-tracker.\n\nSee also an [explanatory blog-post](https://zverok.space/blog/2024-11-16-elixir-pipes.html).\n\n## Usage\n\nDon’t. Really. The code is really naive, tested only for simple cases, and is not intended as a library that will be relied upon. It is an experiment.\n\nBut if you want to play, you can install it as a gem:\n\n```bash\ngem install not_a_pipe\n```\n\n...and then follow the example above.\n\n## Benchmarks\n\nSee `benchmark.rb`. Compared versions are:\n* “naive” Ruby code which puts values into intermediate variables;\n* `.then`-based Ruby version that chains everything in one statement;\n* `not_a_pipe` version\n* [pipe_envy](https://github.com/hopsoft/pipe_envy)-based solution, which is pretty simple (allows to join callable objects with `\u003e\u003e`)\n* [pipe_operator](https://github.com/LendingHome/pipe_operator)-based solution, which is impressively witty looking but requires an extensive implementation with “proxy objects”\n\n```\nruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-linux]\nWarming up --------------------------------------\n               naive     3.000 i/100ms\n               .then     3.000 i/100ms\n          not_a_pipe     4.000 i/100ms\n       pipe_operator     1.000 i/100ms\n           pipe_envy     1.000 i/100ms\nCalculating -------------------------------------\n               naive     18.488 (± 5.4%) i/s   (54.09 ms/i) -     93.000 in   5.067112s\n               .then     15.622 (± 6.4%) i/s   (64.01 ms/i) -     78.000 in   5.019804s\n          not_a_pipe     18.140 (± 5.5%) i/s   (55.13 ms/i) -     92.000 in   5.083882s\n       pipe_operator      1.520 (± 0.0%) i/s  (657.81 ms/i) -      8.000 in   5.266537s\n           pipe_envy      7.296 (±13.7%) i/s  (137.06 ms/i) -     37.000 in   5.098091s\n\nComparison:\n               naive:       18.5 i/s\n          not_a_pipe:       18.1 i/s - same-ish: difference falls within error\n               .then:       15.6 i/s - 1.18x  slower\n           pipe_envy:        7.3 i/s - 2.53x  slower\n       pipe_operator:        1.5 i/s - 12.16x  slower\n```\n\nNote that `not_a_pipe` is the _fastest_ version, on par only with “naive” verbose Ruby code with intermediate variables, and without `.then`-chaining (truth be told, on various runs `.then`-based version is frequently “sam-ish”). The rewrite-on-load approach is a rare way to introduce a DSL without _any_ performance penalty.\n\n## Credits\n\n* Implementation: [Victor Shepelev aka zverok](https://zverok.space)\n* The syntax is proposed by Alexandre Magro [on Ruby bug-tracker](https://bugs.ruby-lang.org/issues/20770#note-34)\n* The AST-rewriting approach is inspired by Python’s [pipe_operator](https://github.com/Jordan-Kowal/pipe-operator) library\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzverok%2Fnot_a_pipe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzverok%2Fnot_a_pipe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzverok%2Fnot_a_pipe/lists"}