Repository: oxinabox/Pipe.jl Branch: master Commit: f721896da1fd Files: 8 Total size: 10.7 KB Directory structure: gitextract_oweatpk6/ ├── .github/ │ └── workflows/ │ └── TagBot.yml ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── Project.toml ├── README.md ├── src/ │ └── Pipe.jl └── test/ └── runtests.jl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/TagBot.yml ================================================ name: TagBot on: schedule: - cron: 0 * * * * jobs: TagBot: runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ *.jl.cov *.jl.mem ================================================ FILE: .travis.yml ================================================ language: julia os: - linux - osx julia: - 1.0 - 1.2 - 1.3 - 1.4 - nightly notifications: email: false # uncomment the following lines to override the default test script #script: # - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi # - julia --check-bounds=yes -e 'Pkg.clone(pwd()); Pkg.build("Pipe"); Pkg.test("Pipe"; coverage=true)' ================================================ FILE: LICENSE.md ================================================ The Pipe.jl package is licensed under the MIT "Expat" License: > Copyright (c) 2015: Lyndon White. > > Permission is hereby granted, free of charge, to any person obtaining > a copy of this software and associated documentation files (the > "Software"), to deal in the Software without restriction, including > without limitation the rights to use, copy, modify, merge, publish, > distribute, sublicense, and/or sell copies of the Software, and to > permit persons to whom the Software is furnished to do so, subject to > the following conditions: > > The above copyright notice and this permission notice shall be > included in all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Project.toml ================================================ name = "Pipe" uuid = "b98c9c47-44ae-5843-9183-064241ee97a0" version = "1.3.0" [compat] julia = "1" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Test"] ================================================ FILE: README.md ================================================ # Pipe - Julia 1.0: [![Build Status 1.0](https://travis-matrix-badges.herokuapp.com/repos/oxinabox/Pipe.jl/branches/master/1)](https://travis-ci.org/oxinabox/Pipe.jl) - Julia 1.2: [![Build Status 1.2](https://travis-matrix-badges.herokuapp.com/repos/oxinabox/Pipe.jl/branches/master/2)](https://travis-ci.org/oxinabox/Pipe.jl) - Julia 1.3: [![Build Status 1.3](https://travis-matrix-badges.herokuapp.com/repos/oxinabox/Pipe.jl/branches/master/3)](https://travis-ci.org/oxinabox/Pipe.jl) - Julia 1.4: [![Build Status 1.4](https://travis-matrix-badges.herokuapp.com/repos/oxinabox/Pipe.jl/branches/master/4)](https://travis-ci.org/oxinabox/Pipe.jl) - Julia Nightly: [![Build Status Nightly](https://travis-matrix-badges.herokuapp.com/repos/oxinabox/Pipe.jl/branches/master/5)](https://travis-ci.org/oxinabox/Pipe.jl) ## Usage Load the package via ```julia using Pipe: @pipe ``` (Just doing `using Pipe` will give an error about a name conflict with the `Base.Pipe` type.) Place `@pipe` at the start of the line for which you want "advanced piping functionality" to work. This works the same as Julia piping, except if you place a underscore in the right-hand expression, it will be replaced with the result of the left-hand expression. So: ```julia @pipe a |> b(x,_) # b(x,a) NOT: (b(x,_))(a) ``` Futher the `_` can be unpacked, called, deindexed, etc. ```julia @pipe a |> b(_...) # b(a...) @pipe a |> b(_(1, 2)) # b(a(1,2)) @pipe a |> b(_[3]) # b(a[3]) ``` This last can be used for interacting with multiple returned values. In general, however, this is frowned upon. Generally, a pipeline is good for expressing a logical flow data through Single Input Single Output functions. When you deindex multiple times, that is case of working with Multiple Input Multiple Output functions. In that case it is likely more clear to create named variables, and call the functions normally in sequence. There is also a performace cost for doing multiple deindexes (see below). For example: ```julia function get_angle(rise, run) atan(rise / run) end @pipe (2,4) |> get_angle(_[1],_[2]) # 0.4636476090008061 get_angle(2,4) # 0.4636476090008061 (Note: the ordinary way is much clearer) ``` However, for each `_` in the right hand side of the `|>`, the left hand side will be called. This can incur a performance cost. ```julia function ratio(value, lr, rr) println("slitting on ratio $lr:$rr") value * lr / (lr + rr), value * rr / (lr + rr) end function percent(left, right) left / right * 100 end @pipe 10 |> ratio(_,4,1) |> percent(_[1],_[2]) # 400.0, outputs splitting on ratio 4:1 Twice @pipe 10 |> ratio(_,4,1) |> percent(_...) # 400.0, outputs splitting on ratio 4:1 Once ``` --------------------- ## See Also: - [List of similar/related works](https://github.com/JuliaLang/julia/issues/5571#issuecomment-205754539) ================================================ FILE: src/Pipe.jl ================================================ module Pipe #Reflow piped things replacing the _ in the next section export @pipe const PLACEHOLDER = :_ function replace(arg::Any, target) arg #Normally do nothing end function replace(arg::Symbol, target) if arg==PLACEHOLDER target else arg end end function replace(arg::Expr, target) rep = copy(arg) rep.args = map(x->replace(x, target), rep.args) rep end function rewrite(ff::Expr, target) rep_args = map(x->replace(x, target), ff.args) if ff.args != rep_args #_ subsitution ff.args = rep_args return ff end #No subsitution was done (no _ found) #Apply to a function that is being returned by ff, #(ff could be a function call or something more complex) rewrite_apply(ff,target) end function rewrite_broadcasted(ff::Expr, target) temp_var = gensym() rep_args = map(x->replace(x, temp_var), ff.args) if ff.args != rep_args #_ subsitution ff.args = rep_args return :($temp_var->$ff) end #No subsitution was done (no _ found) #Apply to a function that is being returned by ff, #(ff could be a function call or something more complex) rewrite_apply_broadcasted(ff,target) end function rewrite_apply(ff, target) :($ff($target)) #function application end function rewrite_apply_broadcasted(ff, target) temp_var = gensym() :($temp_var->$ff($temp_var)) end function rewrite(ff::Symbol, target) if ff==PLACEHOLDER target else rewrite_apply(ff,target) end end function rewrite_broadcasted(ff::Symbol, target) if ff==PLACEHOLDER target else rewrite_apply_broadcasted(ff,target) end end function funnel(ee::Any) #Could be a Symbol could be a literal ee #first (left most) input end function funnel(ee::Expr) if (ee.args[1]==:|>) target = funnel(ee.args[2]) #Recurse rewrite(ee.args[3],target) elseif (ee.args[1]==:.|>) target = funnel(ee.args[2]) #Recurse rewritten = rewrite_broadcasted(ee.args[3],target) ee.args[3] = rewritten ee else #Not in a piping situtation ee #make no change end end macro pipe(ee) esc(funnel(ee)) end end ================================================ FILE: test/runtests.jl ================================================ using Pipe using Test _macroexpand(q) = macroexpand(Main, q) rml! = Base.remove_linenums! # performs linenum removal and temp variable replacing to avoid different names of temp variables in different julia versions stringify_expr(e::Expr) = replace(string(rml!(e)), r"##\d{3}"=>"##000") pipe_equals(e1::Expr, e2::Expr) = stringify_expr(_macroexpand(e1)) == stringify_expr(e2) #No change to nonpipes functionality @test _macroexpand( :(@pipe a) ) == :a #doesn't change single inputs @test _macroexpand( :(@pipe b(a)) ) == :(b(a)) #doesn't change inputs that a function applications #Compatable with Julia 1.3 piping functionality @test _macroexpand( :(@pipe a|>b) ) == :(b(a)) #basic @test _macroexpand( :(@pipe a|>b|>c) ) == :(c(b(a))) #Keeps chaining 3 @test _macroexpand( :(@pipe a|>b|>c|>d) ) == :(d(c(b(a)))) #Keeps chaining 4 @test _macroexpand( :(@pipe a|>b(x)) ) == :((b(x))(a)) #applying to function calls returning functions @test _macroexpand( :(@pipe a(x)|>b ) ) == :(b(a(x))) #feeding functioncall results on wards @test _macroexpand(:(@pipe 1|>a)) ==:(a(1)) #Works with literals (int) @test _macroexpand(:(@pipe "foo"|>a)) == :(a("foo")) #Works with literal (string) @test _macroexpand( :(@pipe a|>bb[2])) == :((bb[2])(a)) #Should work with RHS that is a array reference #Marked locations @test _macroexpand( :(@pipe a |> _)) == :(a) #Identity works @test _macroexpand( :(@pipe a |> _[b])) == :(a[b]) #Indexing works @test _macroexpand( :(@pipe a|>b(_) ) ) == :(b(a)) #Marked location only @test _macroexpand( :(@pipe a|>b(x,_) ) ) == :(b(x,a)) # marked 2nd (and last) @test _macroexpand( :(@pipe a|>b(_,x) ) ) == :(b(a,x)) # marked first @test _macroexpand( :(@pipe a|>b(_,_) ) ) == :(b(a,a)) # marked double (Not certain if this is a good idea) @test _macroexpand( :(@pipe a|>bb[2](x,_))) == :((bb[2])(x,a)) #Should work with RHS that is a array reference #Macros and blocks macro testmacro(arg, n) esc(:($arg + $n)) end @test _macroexpand( :(@pipe a |> @testmacro _ 3 ) ) == :(a + 3) # Can pipe into macros @test _macroexpand( :(@pipe a |> begin b = _; c + b + _ end )) == :( begin b = a; c + b + a end) #marked Unpacking @test _macroexpand( :(@pipe a|>b(_...) ) ) == :(b(a...)) # Unpacking @test _macroexpand( :(@pipe a|>bb[2](_...))) == :((bb[2])(a...)) #Should work with RHS of arry ref and do unpacking #Mixing modes @test _macroexpand( :(@pipe a|>b|>c(_) ) ) == :(c(b(a))) @test _macroexpand( :(@pipe a|>b(x,_)|>c|>d(_,y) ) ) == :(d(c(b(x,a)),y)) @test _macroexpand( :(@pipe a|>b(xb,_)|>c|>d(_,xd)|>e(xe) |>f(xf,_,yf)|>_[i] ) ) == :(f(xf,(e(xe))(d(c(b(xb,a)),xd)),yf)[i]) #Very Complex # broadcasting vars = 1:10 .|> y->gensym() # Julia < 1.3 changes how Symbols are stringified so we compute the representation here @test pipe_equals(:(@pipe 1:10 .|> _*2 ), :(1:10 .|> $(vars[1])->$(vars[1]) * 2)) @test pipe_equals(:(@pipe 1:10 .|> fn ), :(1:10 .|> $(vars[2])->fn($(vars[2])))) @test pipe_equals(:(@pipe a .|> fn .|> _*2 ), :(a .|> ($(vars[3])->fn($(vars[3]))) .|> ($(vars[4])->$(vars[4])*2))) @test pipe_equals(:(@pipe a .|> fn |> _*2 ), :((a .|> $(vars[5])->fn($(vars[5]))) * 2)) @test pipe_equals(:(@pipe [1,2,2] |> atan.([10,20,30], _) ), :(atan.([10,20,30], [1,2,2]))) @test pipe_equals(:(@pipe [1,2,2] .|> atan.([10,20,30], _) ), :([1,2,2] .|> $(vars[6])->atan.([10,20,30], $(vars[6])))) @test pipe_equals(:(@pipe fn |> _.(1:2) ), :(fn.(1:2))) @test pipe_equals(:(@pipe fn .|> _.(1:2) ), :(fn .|> $(vars[7])->$(vars[7]).(1:2))) @test pipe_equals(:(@pipe [true,false] .|> ! ), :([true, false] .|> $(vars[8])->!$(vars[8]))) @test pipe_equals(:(@pipe [1, 2] |> .+(_, x) ), :([1, 2] .+ x)) @test pipe_equals(:(@pipe [1, 2] |> _ .+ x ), :([1, 2] .+ x)) @test pipe_equals(:(@pipe [1, 2] .|> .+(_, x) ), :([1, 2] .|> $(vars[9])->$(vars[9]).+x)) @test pipe_equals(:(@pipe [1, 2] .|> _ .+ x ), :([1, 2] .|> $(vars[10])->$(vars[10]).+x))