From cdc3e732473c328e6969712888bf662d79c92e1b Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 09:57:35 -0500 Subject: [PATCH 01/16] Enable multiple function composition in prefix form Document the prefix form and extend the prefix form to support composition of 3 or more functions. If there is favorable sentiment for this then I can also add tests. There was discussion in 2016 about related issues but I don't see anything more recent https://github.com/JuliaLang/julia/pull/17184 --- base/operators.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index 51105d21c2816..1a0e46458e0a3 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -827,6 +827,10 @@ julia> [1:5;] |> x->x.^2 |> sum |> inv Compose functions: i.e. `(f ∘ g)(args...)` means `f(g(args...))`. The `∘` symbol can be entered in the Julia REPL (and most editors, appropriately configured) by typing `\\circ`. +Function composition also works in prefix form: `∘(f,g)` is the same as `f ∘ g`. +The prefix form also supports multiple function composition: `∘(f,g,h) = f ∘ g ∘ h` +and splatting for an iterable with functions `∘(fs...)` + # Examples ```jldoctest julia> map(uppercase∘first, ["apple", "banana", "carrot"]) @@ -838,6 +842,9 @@ julia> map(uppercase∘first, ["apple", "banana", "carrot"]) """ ∘(f, g) = (x...)->f(g(x...)) +∘(f::Function, g::Function, h::Function) = ∘(f, ∘(g, h)) +∘(f::Function, g::Function, h::Function, is::Function...) = ∘(∘(f, g), h, is...) + """ !f::Function From 7ffb2d960199454aa89233a1561683919fb848cd Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 10:42:00 -0500 Subject: [PATCH 02/16] Refine multiple function composition Thanks for the feedback. Done. Also added a single doctest that should suffice for codecov. --- base/operators.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 1a0e46458e0a3..d1721f68c19cf 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -838,12 +838,16 @@ julia> map(uppercase∘first, ["apple", "banana", "carrot"]) 'A' 'B' 'C' + +julia> fs = (x -> 2x, x -> x/2, x -> x-1, x -> x+1); f = ∘(fs...); f(3) == 3 +true ``` """ ∘(f, g) = (x...)->f(g(x...)) -∘(f::Function, g::Function, h::Function) = ∘(f, ∘(g, h)) -∘(f::Function, g::Function, h::Function, is::Function...) = ∘(∘(f, g), h, is...) +∘(f, g, h) = ∘(f ∘ g, h) +∘(f, g, h, is...) = ∘(∘(f, g), h, is...) + """ From b38c4417b9f148d8b4d1a02fa7d2ca34680ba79d Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 11:33:59 -0500 Subject: [PATCH 03/16] Update punctuation and spacing. I also removed "for functions" because composition can work more generally it seems. --- base/operators.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index d1721f68c19cf..ea7325c125baa 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -829,7 +829,7 @@ entered in the Julia REPL (and most editors, appropriately configured) by typing Function composition also works in prefix form: `∘(f,g)` is the same as `f ∘ g`. The prefix form also supports multiple function composition: `∘(f,g,h) = f ∘ g ∘ h` -and splatting for an iterable with functions `∘(fs...)` +and splatting `∘(fs...)` for an iterable. # Examples ```jldoctest @@ -844,12 +844,9 @@ true ``` """ ∘(f, g) = (x...)->f(g(x...)) - ∘(f, g, h) = ∘(f ∘ g, h) ∘(f, g, h, is...) = ∘(∘(f, g), h, is...) - - """ !f::Function From dee3076ad7af57af50cc0139028e447c741f1dfc Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:10:30 -0500 Subject: [PATCH 04/16] Reword comment Co-Authored-By: Stefan Karpinski --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index ea7325c125baa..e2561a688c035 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -828,7 +828,7 @@ Compose functions: i.e. `(f ∘ g)(args...)` means `f(g(args...))`. The `∘` sy entered in the Julia REPL (and most editors, appropriately configured) by typing `\\circ`. Function composition also works in prefix form: `∘(f,g)` is the same as `f ∘ g`. -The prefix form also supports multiple function composition: `∘(f,g,h) = f ∘ g ∘ h` +The prefix form also supports composition of multiple functions: `∘(f,g,h) = f ∘ g ∘ h` and splatting `∘(fs...)` for an iterable. # Examples From d7513a520553b4b5455a904235791eba789102bf Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:11:41 -0500 Subject: [PATCH 05/16] Expand doctest I was trying to minimize number of lines changed :) Co-Authored-By: Stefan Karpinski --- base/operators.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index e2561a688c035..8fe58159f00dc 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -839,7 +839,15 @@ julia> map(uppercase∘first, ["apple", "banana", "carrot"]) 'B' 'C' -julia> fs = (x -> 2x, x -> x/2, x -> x-1, x -> x+1); f = ∘(fs...); f(3) == 3 +julia> fs = [ + x -> 2x + x -> x/2 + x -> x-1 + x -> x+1 + ]; + +julia> ∘(fs...)(3) +3.0 true ``` """ From 4b5eec87b01b20ae63da0e9a59f76d1afb80ba61 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:25:31 -0500 Subject: [PATCH 06/16] Simplify to a single new line --- base/operators.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 8fe58159f00dc..13c5618b2e720 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -852,8 +852,7 @@ true ``` """ ∘(f, g) = (x...)->f(g(x...)) -∘(f, g, h) = ∘(f ∘ g, h) -∘(f, g, h, is...) = ∘(∘(f, g), h, is...) +∘(f, g, h...) = ∘(f ∘ g, h...) """ !f::Function From 96fd5d7cf8f7a7e7effb699f13b3661e0cfd68a3 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:29:34 -0500 Subject: [PATCH 07/16] Add space Co-Authored-By: Stefan Karpinski --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 13c5618b2e720..299d0d83e1a50 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -827,7 +827,7 @@ julia> [1:5;] |> x->x.^2 |> sum |> inv Compose functions: i.e. `(f ∘ g)(args...)` means `f(g(args...))`. The `∘` symbol can be entered in the Julia REPL (and most editors, appropriately configured) by typing `\\circ`. -Function composition also works in prefix form: `∘(f,g)` is the same as `f ∘ g`. +Function composition also works in prefix form: `∘(f, g)` is the same as `f ∘ g`. The prefix form also supports composition of multiple functions: `∘(f,g,h) = f ∘ g ∘ h` and splatting `∘(fs...)` for an iterable. From 0fea3fbfa300c1b7a1358f63b32215761fb8de37 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:34:07 -0500 Subject: [PATCH 08/16] More spacing in docstring --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 299d0d83e1a50..488e64680672c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -828,7 +828,7 @@ Compose functions: i.e. `(f ∘ g)(args...)` means `f(g(args...))`. The `∘` sy entered in the Julia REPL (and most editors, appropriately configured) by typing `\\circ`. Function composition also works in prefix form: `∘(f, g)` is the same as `f ∘ g`. -The prefix form also supports composition of multiple functions: `∘(f,g,h) = f ∘ g ∘ h` +The prefix form also supports composition of multiple functions: `∘(f, g, h) = f ∘ g ∘ h` and splatting `∘(fs...)` for an iterable. # Examples From 95f1f894eef638aa4c427d433f79e4add9ba774c Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:37:14 -0500 Subject: [PATCH 09/16] Cut also Co-Authored-By: Stefan Karpinski --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 488e64680672c..ac03651a91bb8 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -828,7 +828,7 @@ Compose functions: i.e. `(f ∘ g)(args...)` means `f(g(args...))`. The `∘` sy entered in the Julia REPL (and most editors, appropriately configured) by typing `\\circ`. Function composition also works in prefix form: `∘(f, g)` is the same as `f ∘ g`. -The prefix form also supports composition of multiple functions: `∘(f, g, h) = f ∘ g ∘ h` +The prefix form supports composition of multiple functions: `∘(f, g, h) = f ∘ g ∘ h` and splatting `∘(fs...)` for an iterable. # Examples From c087b2c1f6cd174a8aada8a1703a5bda3f7c608e Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:41:24 -0500 Subject: [PATCH 10/16] Refine docstring I was avoiding the term "function" because an earlier version had unnecessary `Function` typing but I agree that the description is more clear this way. Co-Authored-By: Stefan Karpinski --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index ac03651a91bb8..20be5c4f16fd9 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -829,7 +829,7 @@ entered in the Julia REPL (and most editors, appropriately configured) by typing Function composition also works in prefix form: `∘(f, g)` is the same as `f ∘ g`. The prefix form supports composition of multiple functions: `∘(f, g, h) = f ∘ g ∘ h` -and splatting `∘(fs...)` for an iterable. +and splatting `∘(fs...)` for composing an iterable collection of functions. # Examples ```jldoctest From 58b83ec0e00c2aa77b5582e4070a871c13bce056 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 15:53:37 -0500 Subject: [PATCH 11/16] Multiple function composition per https://github.com/JuliaLang/julia/pull/33568 --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index a47056e19127a..907364e91792d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,9 @@ New language features * `import` now allows quoted symbols, e.g. `import Base.:+` ([#33158]). +* Function composition now supports multiple functions: `∘(f, g, h) = f ∘ g ∘ h` +and splatting `∘(fs...)` with an iterable collection of functions `fs` ([#33568]). + Language changes ---------------- From 390ff0057ea939112b5e5eda74f37a8e749c9d96 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 16:07:12 -0500 Subject: [PATCH 12/16] Add compat 1.4 to docstring --- base/operators.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index 20be5c4f16fd9..cf13f0b56d980 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -831,6 +831,9 @@ Function composition also works in prefix form: `∘(f, g)` is the same as `f The prefix form supports composition of multiple functions: `∘(f, g, h) = f ∘ g ∘ h` and splatting `∘(fs...)` for composing an iterable collection of functions. +!!!compat "Julia 1.4" + Multiple function composition requires at least Julia 1.4. + # Examples ```jldoctest julia> map(uppercase∘first, ["apple", "banana", "carrot"]) From f44dc851b1928b94da0c4fdc4fbca4c26831e67a Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 16:19:26 -0500 Subject: [PATCH 13/16] Add tests for multiple function composition --- test/operators.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/operators.jl b/test/operators.jl index 467d3aa755e2c..f0635a46d557c 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -105,9 +105,12 @@ Base.convert(::Type{T19714}, ::Int) = T19714() Base.promote_rule(::Type{T19714}, ::Type{Int}) = T19714 @test T19714()/1 === 1/T19714() === T19714() -# pr #17155 +# pr #17155 and #33568 @testset "function composition" begin @test (uppercase∘(x->string(x,base=16)))(239487) == "3A77F" + @test (∘(x -> x-2, x -> x-3, x -> x+5))(7) == 7 + fs = [x -> x[1:2], uppercase, lowercase] + @test (∘(fs...))("ABC") == "AB" end @testset "function negation" begin str = randstring(20) From 357297cd145b07c063a892e9a61380a8620b1bf3 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 20:13:54 -0500 Subject: [PATCH 14/16] Refine wording --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 907364e91792d..f7afd2de59434 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ New language features * `import` now allows quoted symbols, e.g. `import Base.:+` ([#33158]). * Function composition now supports multiple functions: `∘(f, g, h) = f ∘ g ∘ h` -and splatting `∘(fs...)` with an iterable collection of functions `fs` ([#33568]). +and splatting `∘(fs...)` for composing an iterable collection of functions ([#33568]). Language changes ---------------- From df3aec8905788cbba616ae227466fc668fbbd157 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 20:15:15 -0500 Subject: [PATCH 15/16] Fix doctest spurious `true` left from older version of doctest --- base/operators.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index cf13f0b56d980..232f6407ed6f5 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -851,7 +851,6 @@ julia> fs = [ julia> ∘(fs...)(3) 3.0 -true ``` """ ∘(f, g) = (x...)->f(g(x...)) From 23e88a4abe062e6bcf92bb8e86412c59fdf83e88 Mon Sep 17 00:00:00 2001 From: Jeff Fessler Date: Tue, 15 Oct 2019 20:17:36 -0500 Subject: [PATCH 16/16] Cut superfluous () --- test/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/operators.jl b/test/operators.jl index f0635a46d557c..58102d7d65a32 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -108,9 +108,9 @@ Base.promote_rule(::Type{T19714}, ::Type{Int}) = T19714 # pr #17155 and #33568 @testset "function composition" begin @test (uppercase∘(x->string(x,base=16)))(239487) == "3A77F" - @test (∘(x -> x-2, x -> x-3, x -> x+5))(7) == 7 + @test ∘(x -> x-2, x -> x-3, x -> x+5)(7) == 7 fs = [x -> x[1:2], uppercase, lowercase] - @test (∘(fs...))("ABC") == "AB" + @test ∘(fs...)("ABC") == "AB" end @testset "function negation" begin str = randstring(20)