From a2b0d84a9bcca71fe211f12daf10c04f191ab411 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 17:54:08 +0100 Subject: [PATCH 01/78] Create Base.FixN for complex functional workflows --- base/operators.jl | 24 +++++++++++ test/functional.jl | 100 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index 4a9daf21c4be5..bb6b3888cece8 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1184,6 +1184,30 @@ end (f::Fix2)(y) = f.f(y, f.x) +""" + FixN(f, x, Val(N)) + +A type representing a partially-applied version of a function +`f`, with the `N`th argument fixed to the value "x". In other words, +`FixN(f, x, Val(N))` behaves similarly to `(y...,) -> f(y[1:N-1]..., x, y[N:end]...)` +""" +struct FixN{F,T,N} <: Function + f::F + x::T + + FixN(f::F, x, ::Val{N}) where {F,N} = new{F,_stable_typeof(x),N}(f, x) + FixN(f::Type{F}, x, ::Val{N}) where {F,N} = new{F,_stable_typeof(x),N}(f, x) +end + +function (f::FixN{F,T,N})(args::Vararg{Any,M}) where {F,T,N,M} + @inline + if M < N - 1 + # (This will compile away) + throw(ArgumentError("expected at least $(N-1) arguments to a `FixN` function with `N=$(N)`")) + end + return f.f(args[begin+0:begin+(N-2)]..., f.x, args[begin+(N-1):end]...) +end + """ isequal(x) diff --git a/test/functional.jl b/test/functional.jl index 3436fb8911cc1..d7e1ae2175e20 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -235,3 +235,103 @@ end let (:)(a,b) = (i for i in Base.:(:)(1,10) if i%2==0) @test Int8[ i for i = 1:2 ] == [2,4,6,8,10] end + +@testset "Basic tests of Fix1, Fix2, and FixN" begin + function test_fix1(Fix1=Base.Fix1) + increment = Fix1(+, 1) + @test increment(5) == 6 + @test increment(-1) == 0 + @test increment(0) == 1 + @test map(increment, [1, 2, 3]) == [2, 3, 4] + + concat_with_hello = Fix1(*, "Hello ") + @test concat_with_hello("World!") == "Hello World!" + # Make sure inference is good: + @inferred concat_with_hello("World!") + + one_divided_by = Fix1(/, 1) + @test one_divided_by(10) == 1/10.0 + @test one_divided_by(-5) == 1/-5.0 + + return nothing + end + + function test_fix2(Fix2=Base.Fix2) + return_second = Fix2((x, y) -> y, 999) + @test return_second(10) == 999 + @inferred return_second(10) + @test return_second(-5) == 999 + + divide_by_two = Fix2(/, 2) + @test map(divide_by_two, (2, 4, 6)) == (1.0, 2.0, 3.0) + @inferred map(divide_by_two, (2, 4, 6)) + + concat_with_world = Fix2(*, " World!") + @test concat_with_world("Hello") == "Hello World!" + @inferred concat_with_world("Hello World!") + + return nothing + end + + # Test with normal Base.Fix1 and Base.Fix2 + test_fix1() + test_fix2() + + # Now, repeat the Fix1 and Fix2 tests, but + # with a FixN lambda function used in their place + test_fix1((op, arg) -> Base.FixN(op, arg, Val(1))) + test_fix2((op, arg) -> Base.FixN(op, arg, Val(2))) + + # Now, we do more complex tests of FixN: + let FixN=Base.FixN + @testset "Argument Fixation" begin + f = (x, y, z) -> x + y * z + fixed_f1 = FixN(f, 10, Val(1)) + @test fixed_f1(2, 3) == 10 + 2 * 3 + + fixed_f2 = FixN(f, 5, Val(2)) + @test fixed_f2(1, 4) == 1 + 5 * 4 + + fixed_f3 = FixN(f, 3, Val(3)) + @test fixed_f3(1, 2) == 1 + 2 * 3 + end + @testset "Helpful errors" begin + g = (x, y) -> x - y + # Test minimum N + fixed_g1 = FixN(g, 100, Val(1)) + @test fixed_g1(40) == 100 - 40 + + # Test maximum N + fixed_g2 = FixN(g, 100, Val(2)) + @test fixed_g2(150) == 150 - 100 + + # One over + fixed_g3 = FixN(g, 100, Val(3)) + @test_throws ArgumentError fixed_g3(1) + @test_throws( + "expected at least 2 arguments to a `FixN` function with `N=3`", + fixed_g3(1) + ) + end + @testset "Type Stability and Inference" begin + h = (x, y) -> x / y + fixed_h = FixN(h, 2.0, Val(2)) + @test @inferred(fixed_h(4.0)) == 2.0 + end + @testset "Interaction with varargs" begin + vararg_f = (x, y, z...) -> x + 10 * y + sum(z; init=zero(x)) + fixed_vararg_f = FixN(vararg_f, 6, Val(2)) + + # Can call with variable number of arguments: + @test fixed_vararg_f(1, 2, 3, 4) == 1 + 10 * 6 + sum((2, 3, 4)) + @inferred fixed_vararg_f(1, 2, 3, 4) + @test fixed_vararg_f(5) == 5 + 10 * 6 + @inferred fixed_vararg_f(5) + end + @testset "Errors should propagate normally" begin + error_f = (x, y) -> sin(x * y) + fixed_error_f = FixN(error_f, Inf, Val(2)) + @test_throws DomainError fixed_error_f(10) + end + end +end From 402ed6c7945304e29da80526df52ce9a07de8612 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 17:59:32 +0100 Subject: [PATCH 02/78] add test for chaining FixN together --- test/functional.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/functional.jl b/test/functional.jl index d7e1ae2175e20..d0962140f9e45 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -333,5 +333,16 @@ end fixed_error_f = FixN(error_f, Inf, Val(2)) @test_throws DomainError fixed_error_f(10) end + @testset "Chaining FixN together" begin + f1 = FixN(*, "1", Val(1)) + f2 = FixN(f1, "2", Val(1)) + f3 = FixN(f2, "3", Val(1)) + @test f3() == "123" + + g1 = FixN(*, "1", Val(2)) + g2 = FixN(g1, "2", Val(2)) + g3 = FixN(g2, "3", Val(2)) + @test g3("") == "123" + end end end From 2c0d91cd17dd6c7112a60aeef792998f9abc13d4 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 20:35:37 +0100 Subject: [PATCH 03/78] allow varargs inside FixN --- base/operators.jl | 24 +++++++++++------------ test/functional.jl | 49 ++++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index bb6b3888cece8..2f3f9ed9325fe 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1185,27 +1185,27 @@ end (f::Fix2)(y) = f.f(y, f.x) """ - FixN(f, x, Val(N)) + FixN(f, Val(N), x...) A type representing a partially-applied version of a function -`f`, with the `N`th argument fixed to the value "x". In other words, -`FixN(f, x, Val(N))` behaves similarly to `(y...,) -> f(y[1:N-1]..., x, y[N:end]...)` +`f`, with the argument or arguments "x" inserted at the `N`th position. In other words, +`FixN(f, Val(N), x1, x2)` behaves similarly to `(y...,) -> f(y[1:N-1]..., x1, x2, y[N:end]...)` + +You may also pass a number of arguments to `FixN`, with `Val(N)`, in which case they will +be inserted at position `N`, `N+1`, and so forth. """ -struct FixN{F,T,N} <: Function +struct FixN{F,N,T<:Tuple} <: Function f::F x::T - FixN(f::F, x, ::Val{N}) where {F,N} = new{F,_stable_typeof(x),N}(f, x) - FixN(f::Type{F}, x, ::Val{N}) where {F,N} = new{F,_stable_typeof(x),N}(f, x) + FixN(f::F, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) + FixN(f::Type{F}, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) end -function (f::FixN{F,T,N})(args::Vararg{Any,M}) where {F,T,N,M} +function (f::FixN{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} @inline - if M < N - 1 - # (This will compile away) - throw(ArgumentError("expected at least $(N-1) arguments to a `FixN` function with `N=$(N)`")) - end - return f.f(args[begin+0:begin+(N-2)]..., f.x, args[begin+(N-1):end]...) + M < N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `FixN` function with `N=$(N)`")) + return f.f(args[begin+0:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) end """ diff --git a/test/functional.jl b/test/functional.jl index d0962140f9e45..77796011f5e49 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -279,34 +279,34 @@ end # Now, repeat the Fix1 and Fix2 tests, but # with a FixN lambda function used in their place - test_fix1((op, arg) -> Base.FixN(op, arg, Val(1))) - test_fix2((op, arg) -> Base.FixN(op, arg, Val(2))) + test_fix1((op, arg) -> Base.FixN(op, Val(1), arg)) + test_fix2((op, arg) -> Base.FixN(op, Val(2), arg)) # Now, we do more complex tests of FixN: let FixN=Base.FixN @testset "Argument Fixation" begin f = (x, y, z) -> x + y * z - fixed_f1 = FixN(f, 10, Val(1)) + fixed_f1 = FixN(f, Val(1), 10) @test fixed_f1(2, 3) == 10 + 2 * 3 - fixed_f2 = FixN(f, 5, Val(2)) + fixed_f2 = FixN(f, Val(2), 5) @test fixed_f2(1, 4) == 1 + 5 * 4 - fixed_f3 = FixN(f, 3, Val(3)) + fixed_f3 = FixN(f, Val(3), 3) @test fixed_f3(1, 2) == 1 + 2 * 3 end @testset "Helpful errors" begin g = (x, y) -> x - y # Test minimum N - fixed_g1 = FixN(g, 100, Val(1)) + fixed_g1 = FixN(g, Val(1), 100) @test fixed_g1(40) == 100 - 40 # Test maximum N - fixed_g2 = FixN(g, 100, Val(2)) + fixed_g2 = FixN(g, Val(2), 100) @test fixed_g2(150) == 150 - 100 # One over - fixed_g3 = FixN(g, 100, Val(3)) + fixed_g3 = FixN(g, Val(3), 100) @test_throws ArgumentError fixed_g3(1) @test_throws( "expected at least 2 arguments to a `FixN` function with `N=3`", @@ -315,12 +315,12 @@ end end @testset "Type Stability and Inference" begin h = (x, y) -> x / y - fixed_h = FixN(h, 2.0, Val(2)) + fixed_h = FixN(h, Val(2), 2.0) @test @inferred(fixed_h(4.0)) == 2.0 end @testset "Interaction with varargs" begin vararg_f = (x, y, z...) -> x + 10 * y + sum(z; init=zero(x)) - fixed_vararg_f = FixN(vararg_f, 6, Val(2)) + fixed_vararg_f = FixN(vararg_f, Val(2), 6) # Can call with variable number of arguments: @test fixed_vararg_f(1, 2, 3, 4) == 1 + 10 * 6 + sum((2, 3, 4)) @@ -330,19 +330,34 @@ end end @testset "Errors should propagate normally" begin error_f = (x, y) -> sin(x * y) - fixed_error_f = FixN(error_f, Inf, Val(2)) + fixed_error_f = FixN(error_f, Val(2), Inf) @test_throws DomainError fixed_error_f(10) end @testset "Chaining FixN together" begin - f1 = FixN(*, "1", Val(1)) - f2 = FixN(f1, "2", Val(1)) - f3 = FixN(f2, "3", Val(1)) + f1 = FixN(*, Val(1), "1") + f2 = FixN(f1, Val(1), "2") + f3 = FixN(f2, Val(1), "3") @test f3() == "123" - g1 = FixN(*, "1", Val(2)) - g2 = FixN(g1, "2", Val(2)) - g3 = FixN(g2, "3", Val(2)) + g1 = FixN(*, Val(2), "1") + g2 = FixN(g1, Val(2), "2") + g3 = FixN(g2, Val(2), "3") @test g3("") == "123" + + # Equivalent to: + h = FixN(*, Val(1), "1", "2", "3") + @test h() == "123" + end + + @testset "varargs inside FixN" begin + lazy_sum = FixN(+, Val(1), 1, 2, 3, 4, 5) + @test lazy_sum() == sum((1, 2, 3, 4, 5)) + + joiner(t...) = join(t, " ") + string_inside = FixN(joiner, Val(3), "third", "fourth", "fifth") + @test string_inside("first", "second", "sixth") == "first second third fourth fifth sixth" + # Still type stable: + @inferred string_inside("first", "second", "sixth") end end end From b25bb9e8c0438f9d8f09779088324fc077d2d7ca Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 21:03:16 +0100 Subject: [PATCH 04/78] remove +0 in FixN --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 2f3f9ed9325fe..712fdc36de028 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1205,7 +1205,7 @@ end function (f::FixN{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} @inline M < N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `FixN` function with `N=$(N)`")) - return f.f(args[begin+0:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) + return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) end """ From c4c9b3325f1736db47ac4a20f8e183cdd54177f3 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 21:29:53 +0100 Subject: [PATCH 05/78] rename to `Fix` function --- base/operators.jl | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 712fdc36de028..b0b2a1fa93c6f 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1185,26 +1185,33 @@ end (f::Fix2)(y) = f.f(y, f.x) """ - FixN(f, Val(N), x...) + Fix(f, n, x...) + Fix(f, Val(n), x...) -A type representing a partially-applied version of a function -`f`, with the argument or arguments "x" inserted at the `N`th position. In other words, -`FixN(f, Val(N), x1, x2)` behaves similarly to `(y...,) -> f(y[1:N-1]..., x1, x2, y[N:end]...)` +A type representing a partially-applied version of a function `f`, with the argument or +arguments "x" inserted at the `n`th position. In other words, `Fix(f, Val(n), x1, x2)` +behaves similarly to `(y...,) -> f(y[1:n-1]..., x1, x2, y[n:end]...)`. -You may also pass a number of arguments to `FixN`, with `Val(N)`, in which case they will -be inserted at position `N`, `N+1`, and so forth. +You may also pass a number of arguments to `Fix`, with `Val(n)`, in which case they will +be inserted at position `n`, `n+1`, and so forth. + +`Val(n)` is preferred for type stability, though `n` as an argument is provided +for convenience. """ -struct FixN{F,N,T<:Tuple} <: Function +struct Fix{F,N,T<:Tuple} <: Function f::F x::T - FixN(f::F, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) - FixN(f::Type{F}, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) + Fix(f::F, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) + Fix(f::Type{F}, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) end -function (f::FixN{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} +Fix(f::F, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) +Fix(f::Type{F}, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) + +function (f::Fix{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} @inline - M < N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `FixN` function with `N=$(N)`")) + M < N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) end From 3cb41337b34b1828dbcffa9a2226e99ae22c66e4 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 21:33:54 +0100 Subject: [PATCH 06/78] ensure Val constructors always inlined --- base/operators.jl | 4 ++-- test/functional.jl | 54 +++++++++++++++++++++++----------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index b0b2a1fa93c6f..9a12649eb731e 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1206,8 +1206,8 @@ struct Fix{F,N,T<:Tuple} <: Function Fix(f::Type{F}, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) end -Fix(f::F, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) -Fix(f::Type{F}, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) +@inline Fix(f::F, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) +@inline Fix(f::Type{F}, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) function (f::Fix{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} @inline diff --git a/test/functional.jl b/test/functional.jl index 77796011f5e49..faa8cb30b74fe 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -236,7 +236,7 @@ let (:)(a,b) = (i for i in Base.:(:)(1,10) if i%2==0) @test Int8[ i for i = 1:2 ] == [2,4,6,8,10] end -@testset "Basic tests of Fix1, Fix2, and FixN" begin +@testset "Basic tests of Fix1, Fix2, and Fix" begin function test_fix1(Fix1=Base.Fix1) increment = Fix1(+, 1) @test increment(5) == 6 @@ -278,49 +278,49 @@ end test_fix2() # Now, repeat the Fix1 and Fix2 tests, but - # with a FixN lambda function used in their place - test_fix1((op, arg) -> Base.FixN(op, Val(1), arg)) - test_fix2((op, arg) -> Base.FixN(op, Val(2), arg)) + # with a Fix lambda function used in their place + test_fix1((op, arg) -> Base.Fix(op, Val(1), arg)) + test_fix2((op, arg) -> Base.Fix(op, Val(2), arg)) - # Now, we do more complex tests of FixN: - let FixN=Base.FixN + # Now, we do more complex tests of Fix: + let Fix=Base.Fix @testset "Argument Fixation" begin f = (x, y, z) -> x + y * z - fixed_f1 = FixN(f, Val(1), 10) + fixed_f1 = Fix(f, Val(1), 10) @test fixed_f1(2, 3) == 10 + 2 * 3 - fixed_f2 = FixN(f, Val(2), 5) + fixed_f2 = Fix(f, Val(2), 5) @test fixed_f2(1, 4) == 1 + 5 * 4 - fixed_f3 = FixN(f, Val(3), 3) + fixed_f3 = Fix(f, Val(3), 3) @test fixed_f3(1, 2) == 1 + 2 * 3 end @testset "Helpful errors" begin g = (x, y) -> x - y # Test minimum N - fixed_g1 = FixN(g, Val(1), 100) + fixed_g1 = Fix(g, Val(1), 100) @test fixed_g1(40) == 100 - 40 # Test maximum N - fixed_g2 = FixN(g, Val(2), 100) + fixed_g2 = Fix(g, Val(2), 100) @test fixed_g2(150) == 150 - 100 # One over - fixed_g3 = FixN(g, Val(3), 100) + fixed_g3 = Fix(g, Val(3), 100) @test_throws ArgumentError fixed_g3(1) @test_throws( - "expected at least 2 arguments to a `FixN` function with `N=3`", + "expected at least 2 arguments to a `Fix` function with `N=3`", fixed_g3(1) ) end @testset "Type Stability and Inference" begin h = (x, y) -> x / y - fixed_h = FixN(h, Val(2), 2.0) + fixed_h = Fix(h, Val(2), 2.0) @test @inferred(fixed_h(4.0)) == 2.0 end @testset "Interaction with varargs" begin vararg_f = (x, y, z...) -> x + 10 * y + sum(z; init=zero(x)) - fixed_vararg_f = FixN(vararg_f, Val(2), 6) + fixed_vararg_f = Fix(vararg_f, Val(2), 6) # Can call with variable number of arguments: @test fixed_vararg_f(1, 2, 3, 4) == 1 + 10 * 6 + sum((2, 3, 4)) @@ -330,31 +330,31 @@ end end @testset "Errors should propagate normally" begin error_f = (x, y) -> sin(x * y) - fixed_error_f = FixN(error_f, Val(2), Inf) + fixed_error_f = Fix(error_f, Val(2), Inf) @test_throws DomainError fixed_error_f(10) end - @testset "Chaining FixN together" begin - f1 = FixN(*, Val(1), "1") - f2 = FixN(f1, Val(1), "2") - f3 = FixN(f2, Val(1), "3") + @testset "Chaining Fix together" begin + f1 = Fix(*, Val(1), "1") + f2 = Fix(f1, Val(1), "2") + f3 = Fix(f2, Val(1), "3") @test f3() == "123" - g1 = FixN(*, Val(2), "1") - g2 = FixN(g1, Val(2), "2") - g3 = FixN(g2, Val(2), "3") + g1 = Fix(*, Val(2), "1") + g2 = Fix(g1, Val(2), "2") + g3 = Fix(g2, Val(2), "3") @test g3("") == "123" # Equivalent to: - h = FixN(*, Val(1), "1", "2", "3") + h = Fix(*, Val(1), "1", "2", "3") @test h() == "123" end - @testset "varargs inside FixN" begin - lazy_sum = FixN(+, Val(1), 1, 2, 3, 4, 5) + @testset "varargs inside Fix" begin + lazy_sum = Fix(+, Val(1), 1, 2, 3, 4, 5) @test lazy_sum() == sum((1, 2, 3, 4, 5)) joiner(t...) = join(t, " ") - string_inside = FixN(joiner, Val(3), "third", "fourth", "fifth") + string_inside = Fix(joiner, Val(3), "third", "fourth", "fifth") @test string_inside("first", "second", "sixth") == "first second third fourth fifth sixth" # Still type stable: @inferred string_inside("first", "second", "sixth") From 98672420bb307cc08f0884cc3f4bb8adeaf474ca Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 21:42:45 +0100 Subject: [PATCH 07/78] test for number of arguments edgecase --- base/operators.jl | 2 +- test/functional.jl | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 9a12649eb731e..d5391bcaf5a6f 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1211,7 +1211,7 @@ end function (f::Fix{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} @inline - M < N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + M < N - 1 || N == 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) end diff --git a/test/functional.jl b/test/functional.jl index faa8cb30b74fe..398d8f893afbd 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -348,6 +348,17 @@ end h = Fix(*, Val(1), "1", "2", "3") @test h() == "123" end + @testset "with integer rather than Val" begin + function f(x, y) + g = Fix(1, x, y) do x, y + x = x^2 + y = y * 3.5 + x + y + end + g() + end + @inferred f(1, 2) + end @testset "varargs inside Fix" begin lazy_sum = Fix(+, Val(1), 1, 2, 3, 4, 5) From c52779b033e5c394d94a903c2859ef299913c5d8 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 2 Jun 2024 21:52:32 +0100 Subject: [PATCH 08/78] fix bound --- base/operators.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index d5391bcaf5a6f..b27adab225cb8 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1211,7 +1211,9 @@ end function (f::Fix{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} @inline - M < N - 1 || N == 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + if M < N - 1 && N != 1 + throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + end return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) end From 7cf72032f3229e78163082ea30da0d24bbe884cb Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 3 Jun 2024 01:43:51 +0100 Subject: [PATCH 09/78] allow fixing kwargs in `Base.Fix` as well --- base/operators.jl | 59 +++++++++++++++++++++++++++----------- test/functional.jl | 70 +++++++++++++++++++++++++++++----------------- 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index b27adab225cb8..adb8f35e5d19c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1185,36 +1185,61 @@ end (f::Fix2)(y) = f.f(y, f.x) """ - Fix(f, n, x...) - Fix(f, Val(n), x...) + Fix(f, n, x...; kws...) + Fix(f, Val(n), x...; kws...) + Fix(f; kws...) A type representing a partially-applied version of a function `f`, with the argument or -arguments "x" inserted at the `n`th position. In other words, `Fix(f, Val(n), x1, x2)` -behaves similarly to `(y...,) -> f(y[1:n-1]..., x1, x2, y[n:end]...)`. +arguments "x" inserted at the `n`th position, and "kws" inserted as keyword arguments. +In other words, `Fix(f, 3, x1)` behaves similarly to `(y...) -> f(y[1], y[2], x1, y[3:end]...)`. -You may also pass a number of arguments to `Fix`, with `Val(n)`, in which case they will -be inserted at position `n`, `n+1`, and so forth. +You may also specify keyword arguments to fix. For example, `Fix(sum; dims=1)` +would be equivalent to `(a...; k...) -> sum(a...; dims=1, k...)`. -`Val(n)` is preferred for type stability, though `n` as an argument is provided -for convenience. +You can also pass multiple arguments at once, which will be inserted after +the first. You can also fix both arguments and keyword arguments in this way. +For example, `Fix(g, 2, x1, x2; verbose=true)` would be equivalent to +`(y...; k...) -> g(y[1], x1, x2, y[2:end]...; verbose=true, k...)`. + +When specifying the index, `Val(n)` is preferred for type stability, +though `n` as an argument is provided for convenience and is likely to be inlined +by the compiler. """ -struct Fix{F,N,T<:Tuple} <: Function +struct Fix{F,N,T<:Tuple,K<:NamedTuple} <: Function f::F x::T + kws::K - Fix(f::F, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) - Fix(f::Type{F}, ::Val{N}, x, xs...) where {F,N} = (xt=(x, xs...); new{F,N,_stable_typeof(xt)}(f, xt)) + function Fix(f::F; kws...) where {F} + xt = () + knt = NamedTuple(kws) + new{F,0,_stable_typeof(xt),typeof(knt)}(f, xt, knt) + end + function Fix(f::F, ::Val{N}, x, xs...; kws...) where {F,N} + xt = (x, xs...) + knt = NamedTuple(kws) + new{F,N,_stable_typeof(xt),typeof(knt)}(f, xt, knt) + end + function Fix(f::Type{F}, ::Val{N}, x, xs...; kws...) where {F,N} + xt = (x, xs...) + knt = NamedTuple(kws) + new{F,N,_stable_typeof(xt),typeof(knt)}(f, xt, knt) + end end -@inline Fix(f::F, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) -@inline Fix(f::Type{F}, n::Int, x, xs...) where {F} = Fix(f, Val(n), x, xs...) +@inline Fix(f::F, n::Int, x, xs...; kws...) where {F} = Fix(f, Val(n), x, xs...; kws...) +@inline Fix(f::Type{F}, n::Int, x, xs...; kws...) where {F} = Fix(f, Val(n), x, xs...; kws...) -function (f::Fix{F,N,T})(args::Vararg{Any,M}) where {F,N,T,M} +function (f::Fix{F,N,T,K})(args::Vararg{Any,M}; kws...) where {F,N,T,K,M} @inline - if M < N - 1 && N != 1 - throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + if N > 1 + if M < N - 1 + throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + end + return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...; f.kws..., kws...) + else + return f.f(f.x..., args...; f.kws..., kws...) end - return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...) end """ diff --git a/test/functional.jl b/test/functional.jl index 398d8f893afbd..8b0a4ecac563c 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -285,38 +285,41 @@ end # Now, we do more complex tests of Fix: let Fix=Base.Fix @testset "Argument Fixation" begin - f = (x, y, z) -> x + y * z - fixed_f1 = Fix(f, Val(1), 10) - @test fixed_f1(2, 3) == 10 + 2 * 3 + let f = (x, y, z) -> x + y * z + fixed_f1 = Fix(f, Val(1), 10) + @test fixed_f1(2, 3) == 10 + 2 * 3 - fixed_f2 = Fix(f, Val(2), 5) - @test fixed_f2(1, 4) == 1 + 5 * 4 + fixed_f2 = Fix(f, Val(2), 5) + @test fixed_f2(1, 4) == 1 + 5 * 4 - fixed_f3 = Fix(f, Val(3), 3) - @test fixed_f3(1, 2) == 1 + 2 * 3 + fixed_f3 = Fix(f, Val(3), 3) + @test fixed_f3(1, 2) == 1 + 2 * 3 + end end @testset "Helpful errors" begin - g = (x, y) -> x - y - # Test minimum N - fixed_g1 = Fix(g, Val(1), 100) - @test fixed_g1(40) == 100 - 40 - - # Test maximum N - fixed_g2 = Fix(g, Val(2), 100) - @test fixed_g2(150) == 150 - 100 - - # One over - fixed_g3 = Fix(g, Val(3), 100) - @test_throws ArgumentError fixed_g3(1) - @test_throws( - "expected at least 2 arguments to a `Fix` function with `N=3`", - fixed_g3(1) - ) + let g = (x, y) -> x - y + # Test minimum N + fixed_g1 = Fix(g, Val(1), 100) + @test fixed_g1(40) == 100 - 40 + + # Test maximum N + fixed_g2 = Fix(g, Val(2), 100) + @test fixed_g2(150) == 150 - 100 + + # One over + fixed_g3 = Fix(g, Val(3), 100) + @test_throws ArgumentError fixed_g3(1) + @test_throws( + "expected at least 2 arguments to a `Fix` function with `N=3`", + fixed_g3(1) + ) + end end @testset "Type Stability and Inference" begin - h = (x, y) -> x / y - fixed_h = Fix(h, Val(2), 2.0) - @test @inferred(fixed_h(4.0)) == 2.0 + let h = (x, y) -> x / y + fixed_h = Fix(h, Val(2), 2.0) + @test @inferred(fixed_h(4.0)) == 2.0 + end end @testset "Interaction with varargs" begin vararg_f = (x, y, z...) -> x + 10 * y + sum(z; init=zero(x)) @@ -359,7 +362,22 @@ end end @inferred f(1, 2) end + @testset "with fixed keywords and zero args" begin + sum_1 = Fix(sum; dims=1) + @test sum_1(ones(3, 2)) == [3.0 3.0] + @inferred sum_1(ones(3, 2)) + end + @testset "with both args and kwargs" begin + f = Fix(sum, 1, ones(3, 2); dims=1) + @test f() == [3.0 3.0] + @inferred f() + + g(a, b, c, d; e, f, g) = join((a, b, c, d, e, f, g), " ") + g_fix = Fix(g, 3, "c", "d"; e="e", f="f") + @test g_fix("a", "b"; g="g") == "a b c d e f g" + @inferred g_fix("a", "b"; g="g") + end @testset "varargs inside Fix" begin lazy_sum = Fix(+, Val(1), 1, 2, 3, 4, 5) @test lazy_sum() == sum((1, 2, 3, 4, 5)) From e377640bdc9af9b950c19f926dfae3c04b6cec83 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Mon, 3 Jun 2024 19:57:03 +0100 Subject: [PATCH 10/78] switch to `Fix{n}` syntax --- base/operators.jl | 28 ++++++++++----------------- test/functional.jl | 48 +++++++++++++++++++++++----------------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index adb8f35e5d19c..c1d868af514f3 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1185,27 +1185,22 @@ end (f::Fix2)(y) = f.f(y, f.x) """ - Fix(f, n, x...; kws...) - Fix(f, Val(n), x...; kws...) + Fix{n}(f, x...; kws...) Fix(f; kws...) A type representing a partially-applied version of a function `f`, with the argument or arguments "x" inserted at the `n`th position, and "kws" inserted as keyword arguments. -In other words, `Fix(f, 3, x1)` behaves similarly to `(y...) -> f(y[1], y[2], x1, y[3:end]...)`. +In other words, `Fix{3}(f, x1)` behaves similarly to `(y...) -> f(y[1], y[2], x1, y[3:end]...)`. You may also specify keyword arguments to fix. For example, `Fix(sum; dims=1)` would be equivalent to `(a...; k...) -> sum(a...; dims=1, k...)`. You can also pass multiple arguments at once, which will be inserted after the first. You can also fix both arguments and keyword arguments in this way. -For example, `Fix(g, 2, x1, x2; verbose=true)` would be equivalent to +For example, `Fix{2}(g, x1, x2; verbose=true)` would be equivalent to `(y...; k...) -> g(y[1], x1, x2, y[2:end]...; verbose=true, k...)`. - -When specifying the index, `Val(n)` is preferred for type stability, -though `n` as an argument is provided for convenience and is likely to be inlined -by the compiler. """ -struct Fix{F,N,T<:Tuple,K<:NamedTuple} <: Function +struct Fix{N,F,T<:Tuple,K<:NamedTuple} <: Function f::F x::T kws::K @@ -1213,24 +1208,21 @@ struct Fix{F,N,T<:Tuple,K<:NamedTuple} <: Function function Fix(f::F; kws...) where {F} xt = () knt = NamedTuple(kws) - new{F,0,_stable_typeof(xt),typeof(knt)}(f, xt, knt) + new{0,F,_stable_typeof(xt),typeof(knt)}(f, xt, knt) end - function Fix(f::F, ::Val{N}, x, xs...; kws...) where {F,N} + function Fix{N}(f::F, x, xs...; kws...) where {F,N} xt = (x, xs...) knt = NamedTuple(kws) - new{F,N,_stable_typeof(xt),typeof(knt)}(f, xt, knt) + new{N,F,_stable_typeof(xt),typeof(knt)}(f, xt, knt) end - function Fix(f::Type{F}, ::Val{N}, x, xs...; kws...) where {F,N} + function Fix{N}(f::Type{F}, x, xs...; kws...) where {F,N} xt = (x, xs...) knt = NamedTuple(kws) - new{F,N,_stable_typeof(xt),typeof(knt)}(f, xt, knt) + new{N,F,_stable_typeof(xt),typeof(knt)}(f, xt, knt) end end -@inline Fix(f::F, n::Int, x, xs...; kws...) where {F} = Fix(f, Val(n), x, xs...; kws...) -@inline Fix(f::Type{F}, n::Int, x, xs...; kws...) where {F} = Fix(f, Val(n), x, xs...; kws...) - -function (f::Fix{F,N,T,K})(args::Vararg{Any,M}; kws...) where {F,N,T,K,M} +function (f::Fix{N,F,T,K})(args::Vararg{Any,M}; kws...) where {N,F,T,K,M} @inline if N > 1 if M < N - 1 diff --git a/test/functional.jl b/test/functional.jl index 8b0a4ecac563c..e6425ce50fed3 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -279,35 +279,35 @@ end # Now, repeat the Fix1 and Fix2 tests, but # with a Fix lambda function used in their place - test_fix1((op, arg) -> Base.Fix(op, Val(1), arg)) - test_fix2((op, arg) -> Base.Fix(op, Val(2), arg)) + test_fix1((op, arg) -> Base.Fix{1}(op, arg)) + test_fix2((op, arg) -> Base.Fix{2}(op, arg)) # Now, we do more complex tests of Fix: - let Fix=Base.Fix + let Fix=Fix @testset "Argument Fixation" begin let f = (x, y, z) -> x + y * z - fixed_f1 = Fix(f, Val(1), 10) + fixed_f1 = Fix{1}(f, 10) @test fixed_f1(2, 3) == 10 + 2 * 3 - fixed_f2 = Fix(f, Val(2), 5) + fixed_f2 = Fix{2}(f, 5) @test fixed_f2(1, 4) == 1 + 5 * 4 - fixed_f3 = Fix(f, Val(3), 3) + fixed_f3 = Fix{3}(f, 3) @test fixed_f3(1, 2) == 1 + 2 * 3 end end @testset "Helpful errors" begin let g = (x, y) -> x - y # Test minimum N - fixed_g1 = Fix(g, Val(1), 100) + fixed_g1 = Fix{1}(g, 100) @test fixed_g1(40) == 100 - 40 # Test maximum N - fixed_g2 = Fix(g, Val(2), 100) + fixed_g2 = Fix{2}(g, 100) @test fixed_g2(150) == 150 - 100 # One over - fixed_g3 = Fix(g, Val(3), 100) + fixed_g3 = Fix{3}(g, 100) @test_throws ArgumentError fixed_g3(1) @test_throws( "expected at least 2 arguments to a `Fix` function with `N=3`", @@ -317,13 +317,13 @@ end end @testset "Type Stability and Inference" begin let h = (x, y) -> x / y - fixed_h = Fix(h, Val(2), 2.0) + fixed_h = Fix{2}(h, 2.0) @test @inferred(fixed_h(4.0)) == 2.0 end end @testset "Interaction with varargs" begin vararg_f = (x, y, z...) -> x + 10 * y + sum(z; init=zero(x)) - fixed_vararg_f = Fix(vararg_f, Val(2), 6) + fixed_vararg_f = Fix{2}(vararg_f, 6) # Can call with variable number of arguments: @test fixed_vararg_f(1, 2, 3, 4) == 1 + 10 * 6 + sum((2, 3, 4)) @@ -333,27 +333,27 @@ end end @testset "Errors should propagate normally" begin error_f = (x, y) -> sin(x * y) - fixed_error_f = Fix(error_f, Val(2), Inf) + fixed_error_f = Fix{2}(error_f, Inf) @test_throws DomainError fixed_error_f(10) end @testset "Chaining Fix together" begin - f1 = Fix(*, Val(1), "1") - f2 = Fix(f1, Val(1), "2") - f3 = Fix(f2, Val(1), "3") + f1 = Fix{1}(*, "1") + f2 = Fix{1}(f1, "2") + f3 = Fix{1}(f2, "3") @test f3() == "123" - g1 = Fix(*, Val(2), "1") - g2 = Fix(g1, Val(2), "2") - g3 = Fix(g2, Val(2), "3") + g1 = Fix{2}(*, "1") + g2 = Fix{2}(g1, "2") + g3 = Fix{2}(g2, "3") @test g3("") == "123" # Equivalent to: - h = Fix(*, Val(1), "1", "2", "3") + h = Fix{1}(*, "1", "2", "3") @test h() == "123" end @testset "with integer rather than Val" begin function f(x, y) - g = Fix(1, x, y) do x, y + g = Fix{1}(x, y) do x, y x = x^2 y = y * 3.5 x + y @@ -368,22 +368,22 @@ end @inferred sum_1(ones(3, 2)) end @testset "with both args and kwargs" begin - f = Fix(sum, 1, ones(3, 2); dims=1) + f = Fix{1}(sum, ones(3, 2); dims=1) @test f() == [3.0 3.0] @inferred f() g(a, b, c, d; e, f, g) = join((a, b, c, d, e, f, g), " ") - g_fix = Fix(g, 3, "c", "d"; e="e", f="f") + g_fix = Fix{3}(g, "c", "d"; e="e", f="f") @test g_fix("a", "b"; g="g") == "a b c d e f g" @inferred g_fix("a", "b"; g="g") end @testset "varargs inside Fix" begin - lazy_sum = Fix(+, Val(1), 1, 2, 3, 4, 5) + lazy_sum = Fix{1}(+, 1, 2, 3, 4, 5) @test lazy_sum() == sum((1, 2, 3, 4, 5)) joiner(t...) = join(t, " ") - string_inside = Fix(joiner, Val(3), "third", "fourth", "fifth") + string_inside = Fix{3}(joiner, "third", "fourth", "fifth") @test string_inside("first", "second", "sixth") == "first second third fourth fifth sixth" # Still type stable: @inferred string_inside("first", "second", "sixth") From a2d8605ef3c5bee452f60597dbc20c37fd68fc7e Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Tue, 4 Jun 2024 03:12:21 +0100 Subject: [PATCH 11/78] back to minimal `Fix` implementation --- base/operators.jl | 41 ++++++++--------------------------------- test/functional.jl | 37 ------------------------------------- 2 files changed, 8 insertions(+), 70 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index c1d868af514f3..208e46a96b4e7 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1185,52 +1185,27 @@ end (f::Fix2)(y) = f.f(y, f.x) """ - Fix{n}(f, x...; kws...) - Fix(f; kws...) + Fix{n}(f, x) A type representing a partially-applied version of a function `f`, with the argument or arguments "x" inserted at the `n`th position, and "kws" inserted as keyword arguments. In other words, `Fix{3}(f, x1)` behaves similarly to `(y...) -> f(y[1], y[2], x1, y[3:end]...)`. - -You may also specify keyword arguments to fix. For example, `Fix(sum; dims=1)` -would be equivalent to `(a...; k...) -> sum(a...; dims=1, k...)`. - -You can also pass multiple arguments at once, which will be inserted after -the first. You can also fix both arguments and keyword arguments in this way. -For example, `Fix{2}(g, x1, x2; verbose=true)` would be equivalent to -`(y...; k...) -> g(y[1], x1, x2, y[2:end]...; verbose=true, k...)`. """ -struct Fix{N,F,T<:Tuple,K<:NamedTuple} <: Function +struct Fix{N,F,T} <: Function f::F x::T - kws::K - function Fix(f::F; kws...) where {F} - xt = () - knt = NamedTuple(kws) - new{0,F,_stable_typeof(xt),typeof(knt)}(f, xt, knt) - end - function Fix{N}(f::F, x, xs...; kws...) where {F,N} - xt = (x, xs...) - knt = NamedTuple(kws) - new{N,F,_stable_typeof(xt),typeof(knt)}(f, xt, knt) - end - function Fix{N}(f::Type{F}, x, xs...; kws...) where {F,N} - xt = (x, xs...) - knt = NamedTuple(kws) - new{N,F,_stable_typeof(xt),typeof(knt)}(f, xt, knt) - end + Fix{N}(f::F, x) where {F,N} = new{N,F,_stable_typeof(x)}(f, x) + Fix{N}(f::Type{F}, x) where {F,N} = new{N,F,_stable_typeof(x)}(f, x) end -function (f::Fix{N,F,T,K})(args::Vararg{Any,M}; kws...) where {N,F,T,K,M} +function (f::Fix{N,F,T})(args::Vararg{Any,M}) where {N,F,T,M} @inline if N > 1 - if M < N - 1 - throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) - end - return f.f(args[begin:begin+(N-2)]..., f.x..., args[begin+(N-1):end]...; f.kws..., kws...) + M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...) else - return f.f(f.x..., args...; f.kws..., kws...) + return f.f(f.x, args...) end end diff --git a/test/functional.jl b/test/functional.jl index e6425ce50fed3..0859ebe904852 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -351,42 +351,5 @@ end h = Fix{1}(*, "1", "2", "3") @test h() == "123" end - @testset "with integer rather than Val" begin - function f(x, y) - g = Fix{1}(x, y) do x, y - x = x^2 - y = y * 3.5 - x + y - end - g() - end - @inferred f(1, 2) - end - @testset "with fixed keywords and zero args" begin - sum_1 = Fix(sum; dims=1) - @test sum_1(ones(3, 2)) == [3.0 3.0] - @inferred sum_1(ones(3, 2)) - end - @testset "with both args and kwargs" begin - f = Fix{1}(sum, ones(3, 2); dims=1) - @test f() == [3.0 3.0] - @inferred f() - - g(a, b, c, d; e, f, g) = join((a, b, c, d, e, f, g), " ") - g_fix = Fix{3}(g, "c", "d"; e="e", f="f") - - @test g_fix("a", "b"; g="g") == "a b c d e f g" - @inferred g_fix("a", "b"; g="g") - end - @testset "varargs inside Fix" begin - lazy_sum = Fix{1}(+, 1, 2, 3, 4, 5) - @test lazy_sum() == sum((1, 2, 3, 4, 5)) - - joiner(t...) = join(t, " ") - string_inside = Fix{3}(joiner, "third", "fourth", "fifth") - @test string_inside("first", "second", "sixth") == "first second third fourth fifth sixth" - # Still type stable: - @inferred string_inside("first", "second", "sixth") - end end end From 2491c627ecde61941b6ee58c55edf2e2b83a4fca Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Tue, 4 Jun 2024 04:10:31 +0100 Subject: [PATCH 12/78] clean up docstring with keyword-less version --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 208e46a96b4e7..8a2595105ce81 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1188,8 +1188,8 @@ end Fix{n}(f, x) A type representing a partially-applied version of a function `f`, with the argument or -arguments "x" inserted at the `n`th position, and "kws" inserted as keyword arguments. -In other words, `Fix{3}(f, x1)` behaves similarly to `(y...) -> f(y[1], y[2], x1, y[3:end]...)`. +arguments "x" inserted at the `n`th position. In other words, `Fix{3}(f, x)` behaves +similarly to `(y...,) -> f(y[1], y[2], x, y[3:end]...)`. """ struct Fix{N,F,T} <: Function f::F From 439c5b665865d6897e79b5266a35b5dbf70cabae Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 6 Jun 2024 19:18:23 +0100 Subject: [PATCH 13/78] allow keywords in Fix and rewrite Fix1/Fix2 --- base/operators.jl | 78 ++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 8a2595105ce81..c75ca47a68dfb 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1149,65 +1149,61 @@ julia> filter(!isletter, str) !(f::ComposedFunction{typeof(!)}) = f.inner #allows !!f === f """ - Fix1(f, x) + Fix{n}(f, x; kws...) -A type representing a partially-applied version of the two-argument function -`f`, with the first argument fixed to the value "x". In other words, -`Fix1(f, x)` behaves similarly to `y->f(x, y)`. +A type representing a partially-applied version of a function `f`, with the argument or +arguments "x" inserted at the `n`th position, and any additional keyword arguments inserted +at the end. In other words, `Fix{3}(f, x)` behaves similarly to +`(y1, y2, y3) -> f(y1, y2, x, y3)` for the 4-argument function `f`. -See also [`Fix2`](@ref Base.Fix2). +You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly +to `x -> g(x; a=2)` for a function `g` with one arguments and one keyword argument. """ -struct Fix1{F,T} <: Function +struct Fix{N,F,T,K} <: Function f::F x::T + k::K - Fix1(f::F, x) where {F} = new{F,_stable_typeof(x)}(f, x) - Fix1(f::Type{F}, x) where {F} = new{Type{F},_stable_typeof(x)}(f, x) + Fix(f::F; kws...) where {F} = (k = NamedTuple(kws); new{0,F,typeof(()),typeof(k)}(f, (), k)) + Fix{N}(f::F, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) + Fix{N}(f::Type{F}, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) end -(f::Fix1)(y) = f.f(f.x, y) +function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} + @inline + if N > 1 + M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; f.k..., kws...) + elseif N == 1 + return f.f(f.x, args...; f.k..., kws...) + else + return f.f(args...; f.k..., kws...) + end +end -""" - Fix2(f, x) -A type representing a partially-applied version of the two-argument function -`f`, with the second argument fixed to the value "x". In other words, -`Fix2(f, x)` behaves similarly to `y->f(y, x)`. """ -struct Fix2{F,T} <: Function - f::F - x::T - - Fix2(f::F, x) where {F} = new{F,_stable_typeof(x)}(f, x) - Fix2(f::Type{F}, x) where {F} = new{Type{F},_stable_typeof(x)}(f, x) -end + Fix1(f, x; kws...) -(f::Fix2)(y) = f.f(y, f.x) +A type representing a partially-applied version of the function +`f`, with the first argument fixed to the value "x". In other words, +`Fix1(f, x)` behaves similarly to `y->f(x, y)` for a 2-argument function `f`. +See also [`Fix2`](@ref Base.Fix2) and [`Fix`](@ref Base.Fix). """ - Fix{n}(f, x) +const Fix1{F,T} = Fix{1,F,T} -A type representing a partially-applied version of a function `f`, with the argument or -arguments "x" inserted at the `n`th position. In other words, `Fix{3}(f, x)` behaves -similarly to `(y...,) -> f(y[1], y[2], x, y[3:end]...)`. """ -struct Fix{N,F,T} <: Function - f::F - x::T + Fix2(f, x; kws...) - Fix{N}(f::F, x) where {F,N} = new{N,F,_stable_typeof(x)}(f, x) - Fix{N}(f::Type{F}, x) where {F,N} = new{N,F,_stable_typeof(x)}(f, x) -end +A type representing a partially-applied version of the function +`f`, with the second argument fixed to the value "x". In other words, +`Fix2(f, x)` behaves similarly to `y->f(y, x)` for a 2-argument function `f`. + +See also [`Fix`](@ref Base.Fix). +""" +const Fix2{F,T} = Fix{2,F,T} -function (f::Fix{N,F,T})(args::Vararg{Any,M}) where {N,F,T,M} - @inline - if N > 1 - M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) - return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...) - else - return f.f(f.x, args...) - end -end """ isequal(x) From 4a0e778776bf41f4986cbd39c7c166f16bca968f Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 6 Jun 2024 19:35:35 +0100 Subject: [PATCH 14/78] prevent keywords from being passed to Fix1/Fix2 --- base/operators.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index c75ca47a68dfb..f529601e2158d 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1191,7 +1191,8 @@ A type representing a partially-applied version of the function See also [`Fix2`](@ref Base.Fix2) and [`Fix`](@ref Base.Fix). """ -const Fix1{F,T} = Fix{1,F,T} +const Fix1{F,T} = Fix{1,F,T,@NamedTuple{}} +Fix1(f, x) = Fix{1}(f, x) """ Fix2(f, x; kws...) @@ -1202,7 +1203,8 @@ A type representing a partially-applied version of the function See also [`Fix`](@ref Base.Fix). """ -const Fix2{F,T} = Fix{2,F,T} +const Fix2{F,T} = Fix{2,F,T,@NamedTuple{}} +Fix2(f, x) = Fix{2}(f, x) """ From a473b5ee2b6c10a7ea1cc0a8e5057fb4950434ed Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 6 Jun 2024 20:06:08 +0100 Subject: [PATCH 15/78] use nothing instead of Tuple{} --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index f529601e2158d..8b7f13b4db4e8 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1164,7 +1164,7 @@ struct Fix{N,F,T,K} <: Function x::T k::K - Fix(f::F; kws...) where {F} = (k = NamedTuple(kws); new{0,F,typeof(()),typeof(k)}(f, (), k)) + Fix(f::F; kws...) where {F} = (k = NamedTuple(kws); new{0,F,Nothing,typeof(k)}(f, nothing, k)) Fix{N}(f::F, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) Fix{N}(f::Type{F}, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) end From 758bbcc54d018c754be1138d37df4ab9609643c8 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 7 Jun 2024 13:52:06 +0900 Subject: [PATCH 16/78] update Fix1/Fix2 docstrings --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 8b7f13b4db4e8..b2a67c151e4c5 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1183,7 +1183,7 @@ end """ - Fix1(f, x; kws...) + Fix1(f, x) A type representing a partially-applied version of the function `f`, with the first argument fixed to the value "x". In other words, @@ -1195,7 +1195,7 @@ const Fix1{F,T} = Fix{1,F,T,@NamedTuple{}} Fix1(f, x) = Fix{1}(f, x) """ - Fix2(f, x; kws...) + Fix2(f, x) A type representing a partially-applied version of the function `f`, with the second argument fixed to the value "x". In other words, From e2fc7401efcf76f80defb1b676bc0145ffd75224 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 7 Jun 2024 07:22:51 +0100 Subject: [PATCH 17/78] fix undefined `@NamedTuple` --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index b2a67c151e4c5..55014ee602eb5 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1191,7 +1191,7 @@ A type representing a partially-applied version of the function See also [`Fix2`](@ref Base.Fix2) and [`Fix`](@ref Base.Fix). """ -const Fix1{F,T} = Fix{1,F,T,@NamedTuple{}} +const Fix1{F,T} = Fix{1,F,T,typeof((;))} Fix1(f, x) = Fix{1}(f, x) """ @@ -1203,7 +1203,7 @@ A type representing a partially-applied version of the function See also [`Fix`](@ref Base.Fix). """ -const Fix2{F,T} = Fix{2,F,T,@NamedTuple{}} +const Fix2{F,T} = Fix{2,F,T,typeof((;))} Fix2(f, x) = Fix{2}(f, x) From 9aa7c52816f895746306a3ee74892655f35e9a92 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 7 Jun 2024 07:41:41 +0100 Subject: [PATCH 18/78] constrain `K` to `NamedTuple` --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 55014ee602eb5..dbea0c2de1c97 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1159,14 +1159,14 @@ at the end. In other words, `Fix{3}(f, x)` behaves similarly to You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly to `x -> g(x; a=2)` for a function `g` with one arguments and one keyword argument. """ -struct Fix{N,F,T,K} <: Function +struct Fix{N,F,T,K<:NamedTuple} <: Function f::F x::T k::K Fix(f::F; kws...) where {F} = (k = NamedTuple(kws); new{0,F,Nothing,typeof(k)}(f, nothing, k)) Fix{N}(f::F, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) - Fix{N}(f::Type{F}, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) + Fix{N}(f::Type{F}, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,Type{F},_stable_typeof(x),typeof(k)}(f, x, k)) end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} From b886dd03dd492cfe4e8f531b31016f20b5a927d6 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 7 Jun 2024 07:57:52 +0100 Subject: [PATCH 19/78] extend docstring for Base.Fix --- base/operators.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/operators.jl b/base/operators.jl index dbea0c2de1c97..2d3d161506db9 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1149,6 +1149,7 @@ julia> filter(!isletter, str) !(f::ComposedFunction{typeof(!)}) = f.inner #allows !!f === f """ + Fix(f; kws...) Fix{n}(f, x; kws...) A type representing a partially-applied version of a function `f`, with the argument or From c32c65afe5501e2ee79a50e5423776eb13c634c4 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 7 Jun 2024 08:04:16 +0100 Subject: [PATCH 20/78] update tests for Base.Fix --- test/functional.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/functional.jl b/test/functional.jl index 0859ebe904852..8df9806b9dfc2 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -283,7 +283,7 @@ end test_fix2((op, arg) -> Base.Fix{2}(op, arg)) # Now, we do more complex tests of Fix: - let Fix=Fix + let Fix=Base.Fix @testset "Argument Fixation" begin let f = (x, y, z) -> x + y * z fixed_f1 = Fix{1}(f, 10) @@ -346,10 +346,15 @@ end g2 = Fix{2}(g1, "2") g3 = Fix{2}(g2, "3") @test g3("") == "123" - - # Equivalent to: - h = Fix{1}(*, "1", "2", "3") - @test h() == "123" + end + @testset "With kwargs" begin + sum1 = Base.Fix(sum; dims=1) + x = ones(3, 2) + sum1(x) == [3.0 3.0] + + # Now, with arg and kwargs + cat1 = Base.Fix{1}(cat, ones(2); dims=1) + cat1(ones(3)) == ones(5) end end end From 866c7c709390218481477f204a805c2b1012338a Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 7 Jun 2024 08:19:40 +0100 Subject: [PATCH 21/78] add Base.Fix to docs --- doc/src/base/base.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 03a12e81fe673..5a83ccaa4b54a 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -277,6 +277,7 @@ Base.:(|>) Base.:(∘) Base.ComposedFunction Base.splat +Base.Fix Base.Fix1 Base.Fix2 ``` From a9674c0f8e280e4068d5aaa26050c7b663b214e9 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sat, 8 Jun 2024 21:43:21 +0100 Subject: [PATCH 22/78] add dummy-proofing to `Base.Fix{n}` --- base/operators.jl | 24 ++++++++++++++++++++++-- test/functional.jl | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 2d3d161506db9..a45adee0c0024 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1166,11 +1166,31 @@ struct Fix{N,F,T,K<:NamedTuple} <: Function k::K Fix(f::F; kws...) where {F} = (k = NamedTuple(kws); new{0,F,Nothing,typeof(k)}(f, nothing, k)) - Fix{N}(f::F, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,F,_stable_typeof(x),typeof(k)}(f, x, k)) - Fix{N}(f::Type{F}, x; kws...) where {N,F} = (k = NamedTuple(kws); new{N,Type{F},_stable_typeof(x),typeof(k)}(f, x, k)) + function Fix{N}(f::F, x; kws...) where {N,F} + _validate_fix_args(Val(N)) + k = NamedTuple(kws) + new{convert(Int64, N),F,_stable_typeof(x),typeof(k)}(f, x, k) + end + function Fix{N}(f::Type{F}, x; kws...) where {N,F} + _validate_fix_args(Val(N)) + k = NamedTuple(kws) + new{convert(Int64, N),Type{F},_stable_typeof(x),typeof(k)}(f, x, k) + end +end + +function _validate_fix_args(::Val{N}) where {N} + if !(N isa Integer) + throw(ArgumentError("expected integer `N` parameter in `Fix{N}`")) + elseif N < 1 + throw(ArgumentError("expected `N` to be greater than or equal to 1 in `Fix{N}`")) + end + return nothing end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} + if !isempty(kws) && !isempty(f.k) && !isdisjoint(keys(kws), keys(f.k)) + throw(ArgumentError("found duplicate keyword argument(s) passed to `Fix{N}`")) + end @inline if N > 1 M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) diff --git a/test/functional.jl b/test/functional.jl index 8df9806b9dfc2..23cbab0b4c93f 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -356,5 +356,24 @@ end cat1 = Base.Fix{1}(cat, ones(2); dims=1) cat1(ones(3)) == ones(5) end + @testset "Dummy-proofing" begin + @test_throws ArgumentError Base.Fix{0}(>, 1) + @test_throws "expected `N` to be greater than or equal to 1 in `Fix{N}`" Base.Fix{0}(>, 1) + + @test_throws ArgumentError Base.Fix{0.5}(>, 1) + @test_throws "expected integer `N` parameter in `Fix{N}`" Base.Fix{0.5}(>, 1) + + _get_fix_n(::Fix{N}) where {N} = N + f = Fix{UInt64(1)}(>, 1) + # Will automatically convert + @test _get_fix_n(f) isa Int64 + + # duplicate keywords + sum1 = Base.Fix(sum; dims=1) + x = ones(3, 2) + @test sum1(x) == [3.0 3.0] + @test_throws ArgumentError sum1(x; dims=2) + @test_throws "found duplicate keyword argument(s) passed to `Fix{N}`" sum1(x; dims=2) + end end end From 1825f769f2d4ef430d1cef0e19680961c8a25344 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sat, 8 Jun 2024 21:58:40 +0100 Subject: [PATCH 23/78] simplify validation check --- base/operators.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index a45adee0c0024..39149e6a7ceb9 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1179,11 +1179,8 @@ struct Fix{N,F,T,K<:NamedTuple} <: Function end function _validate_fix_args(::Val{N}) where {N} - if !(N isa Integer) - throw(ArgumentError("expected integer `N` parameter in `Fix{N}`")) - elseif N < 1 - throw(ArgumentError("expected `N` to be greater than or equal to 1 in `Fix{N}`")) - end + N isa Integer || throw(ArgumentError("expected integer `N` parameter in `Fix{N}`")) + N >= 1 || throw(ArgumentError("expected `N` to be greater than or equal to 1 in `Fix{N}`")) return nothing end From 5604ffe77a0df2911241e9af2884d3510b89dee6 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sat, 8 Jun 2024 23:12:13 +0100 Subject: [PATCH 24/78] unify `Fix` constructors with `Union{F,Type{F}}` --- base/operators.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 39149e6a7ceb9..088629c97f5a1 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1165,16 +1165,14 @@ struct Fix{N,F,T,K<:NamedTuple} <: Function x::T k::K - Fix(f::F; kws...) where {F} = (k = NamedTuple(kws); new{0,F,Nothing,typeof(k)}(f, nothing, k)) - function Fix{N}(f::F, x; kws...) where {N,F} - _validate_fix_args(Val(N)) + function Fix(f::Union{F,Type{F}}; kws...) where {F} k = NamedTuple(kws) - new{convert(Int64, N),F,_stable_typeof(x),typeof(k)}(f, x, k) + new{0,(f isa Type ? Type{F} : F),Nothing,typeof(k)}(f, nothing, k) end - function Fix{N}(f::Type{F}, x; kws...) where {N,F} + function Fix{N}(f::Union{F,Type{F}}, x; kws...) where {N,F} _validate_fix_args(Val(N)) k = NamedTuple(kws) - new{convert(Int64, N),Type{F},_stable_typeof(x),typeof(k)}(f, x, k) + new{Int64(N),(f isa Type ? Type{F} : F),_stable_typeof(x),typeof(k)}(f, x, k) end end From 74dcfb1414c4d463bdab1475b03d6adc1a969de3 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sat, 8 Jun 2024 23:24:16 +0100 Subject: [PATCH 25/78] improve readability of validation code --- base/operators.jl | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 088629c97f5a1..de8a5e2f434ac 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1170,25 +1170,17 @@ struct Fix{N,F,T,K<:NamedTuple} <: Function new{0,(f isa Type ? Type{F} : F),Nothing,typeof(k)}(f, nothing, k) end function Fix{N}(f::Union{F,Type{F}}, x; kws...) where {N,F} - _validate_fix_args(Val(N)) + _validate_fix_param(Val(N)) k = NamedTuple(kws) new{Int64(N),(f isa Type ? Type{F} : F),_stable_typeof(x),typeof(k)}(f, x, k) end end -function _validate_fix_args(::Val{N}) where {N} - N isa Integer || throw(ArgumentError("expected integer `N` parameter in `Fix{N}`")) - N >= 1 || throw(ArgumentError("expected `N` to be greater than or equal to 1 in `Fix{N}`")) - return nothing -end - function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} - if !isempty(kws) && !isempty(f.k) && !isdisjoint(keys(kws), keys(f.k)) - throw(ArgumentError("found duplicate keyword argument(s) passed to `Fix{N}`")) - end @inline + _validate_fix_args(Val(N), Val(M)) + _validate_fix_kwargs(f.k, kws) if N > 1 - M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; f.k..., kws...) elseif N == 1 return f.f(f.x, args...; f.k..., kws...) @@ -1197,6 +1189,16 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} end end +function _validate_fix_param(::Val{N}) where {N} + (N isa Integer && N >= 1) || throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) +end +function _validate_fix_args(::Val{N}, ::Val{M}) where {N,M} + N <= 1 || M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) +end +function _validate_fix_kwargs(f_k, kws) + isempty(kws) || isempty(f_k) || isdisjoint(keys(kws), keys(f_k)) || + throw(ArgumentError("found duplicate keyword argument(s) passed to `Fix{N}`")) +end """ Fix1(f, x) From 10b83203203d3ec5aa5b3296026f1e8e192b63fa Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sat, 8 Jun 2024 23:30:38 +0100 Subject: [PATCH 26/78] update tests with allowed behavior --- test/functional.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/functional.jl b/test/functional.jl index 23cbab0b4c93f..f1de920e990ab 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -296,6 +296,12 @@ end @test fixed_f3(1, 2) == 1 + 2 * 3 end end + @testset "allowed empty function" begin + let f = () -> 1 + fixed_f = Fix(f) + @test fixed_f() == 1 + end + end @testset "Helpful errors" begin let g = (x, y) -> x - y # Test minimum N @@ -358,10 +364,10 @@ end end @testset "Dummy-proofing" begin @test_throws ArgumentError Base.Fix{0}(>, 1) - @test_throws "expected `N` to be greater than or equal to 1 in `Fix{N}`" Base.Fix{0}(>, 1) + @test_throws "expected `N` in `Fix{N}` to be integer greater than 0" Base.Fix{0}(>, 1) @test_throws ArgumentError Base.Fix{0.5}(>, 1) - @test_throws "expected integer `N` parameter in `Fix{N}`" Base.Fix{0.5}(>, 1) + @test_throws "expected `N` in `Fix{N}` to be integer greater than 0" Base.Fix{0.5}(>, 1) _get_fix_n(::Fix{N}) where {N} = N f = Fix{UInt64(1)}(>, 1) From 73dd2cf171a5be1105ea2a2339da806307a24723 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sat, 8 Jun 2024 23:52:28 +0100 Subject: [PATCH 27/78] make constructors more readable --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index de8a5e2f434ac..c4f99f08a6d30 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1167,12 +1167,12 @@ struct Fix{N,F,T,K<:NamedTuple} <: Function function Fix(f::Union{F,Type{F}}; kws...) where {F} k = NamedTuple(kws) - new{0,(f isa Type ? Type{F} : F),Nothing,typeof(k)}(f, nothing, k) + new{0,_stable_typeof(f),Nothing,typeof(k)}(f, nothing, k) end function Fix{N}(f::Union{F,Type{F}}, x; kws...) where {N,F} _validate_fix_param(Val(N)) k = NamedTuple(kws) - new{Int64(N),(f isa Type ? Type{F} : F),_stable_typeof(x),typeof(k)}(f, x, k) + new{Int64(N),_stable_typeof(f),_stable_typeof(x),typeof(k)}(f, x, k) end end From fb8ad78548c96c040fa1cdf67f16060f8ba0ab62 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Sun, 9 Jun 2024 21:37:55 +0100 Subject: [PATCH 28/78] tweak docstring --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index c4f99f08a6d30..13b156293c6f7 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1153,7 +1153,7 @@ julia> filter(!isletter, str) Fix{n}(f, x; kws...) A type representing a partially-applied version of a function `f`, with the argument or -arguments "x" inserted at the `n`th position, and any additional keyword arguments inserted +"x" inserted at the `n`th position, and any additional keyword arguments inserted at the end. In other words, `Fix{3}(f, x)` behaves similarly to `(y1, y2, y3) -> f(y1, y2, x, y3)` for the 4-argument function `f`. From de330be7d02ccf79ddc9947931d311ee06c9a2d9 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Sun, 9 Jun 2024 21:38:48 +0100 Subject: [PATCH 29/78] tweak docstring --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 13b156293c6f7..224f6dc99d0b3 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1152,7 +1152,7 @@ julia> filter(!isletter, str) Fix(f; kws...) Fix{n}(f, x; kws...) -A type representing a partially-applied version of a function `f`, with the argument or +A type representing a partially-applied version of a function `f`, with the argument "x" inserted at the `n`th position, and any additional keyword arguments inserted at the end. In other words, `Fix{3}(f, x)` behaves similarly to `(y1, y2, y3) -> f(y1, y2, x, y3)` for the 4-argument function `f`. From a24f389aeca1ea886b27d203e4624e16c7dbe62b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 20 Jun 2024 10:41:29 +0100 Subject: [PATCH 30/78] `Fix` syntax takes symbol as parameter --- base/operators.jl | 77 ++++++++++++++++++++++++++++------------------ test/functional.jl | 4 --- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 224f6dc99d0b3..e39043e149c88 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1149,55 +1149,72 @@ julia> filter(!isletter, str) !(f::ComposedFunction{typeof(!)}) = f.inner #allows !!f === f """ - Fix(f; kws...) - Fix{n}(f, x; kws...) + Fix{n}(f, x) + Fix{kw}(f, x) + Fix(f; [kw]=x) A type representing a partially-applied version of a function `f`, with the argument -"x" inserted at the `n`th position, and any additional keyword arguments inserted -at the end. In other words, `Fix{3}(f, x)` behaves similarly to +"x" fixed at argument `n::Int` or keyword `kw::Symbol`. +In other words, `Fix{3}(f, x)` behaves similarly to `(y1, y2, y3) -> f(y1, y2, x, y3)` for the 4-argument function `f`. -You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly -to `x -> g(x; a=2)` for a function `g` with one arguments and one keyword argument. +You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves +similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keyword argument. +You can also write this as `Fix{:a}(g, 2)`. """ -struct Fix{N,F,T,K<:NamedTuple} <: Function +struct Fix{N,F,T} <: Function f::F x::T - k::K - function Fix(f::Union{F,Type{F}}; kws...) where {F} - k = NamedTuple(kws) - new{0,_stable_typeof(f),Nothing,typeof(k)}(f, nothing, k) + function Fix{N}(f::Union{F,Type{F}}, x) where {N,F} + _N = _standardize_fix_param(Val(N)) + new{_N,_stable_typeof(f),_stable_typeof(x)}(f, x) end - function Fix{N}(f::Union{F,Type{F}}, x; kws...) where {N,F} - _validate_fix_param(Val(N)) - k = NamedTuple(kws) - new{Int64(N),_stable_typeof(f),_stable_typeof(x),typeof(k)}(f, x, k) +end +function Fix(f::Union{F,Type{F}}; kws...) where {F} + if length(kws) != 1 + throw(ArgumentError("`Fix` expects exactly one argument or keyword argument")) end + Fix{only(keys(kws))}(f, only(values(kws))) end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} @inline _validate_fix_args(Val(N), Val(M)) - _validate_fix_kwargs(f.k, kws) - if N > 1 - return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; f.k..., kws...) - elseif N == 1 - return f.f(f.x, args...; f.k..., kws...) - else - return f.f(args...; f.k..., kws...) + _validate_fix_kwargs(Val(N); kws...) + if N isa Integer + if N > 1 + return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) + else # N == 1 + return f.f(f.x, args...; kws...) + end + else # N isa Symbol + f_kws = NamedTuple{(N,)}((f.x,)) + return f.f(args...; f_kws..., kws...) end end -function _validate_fix_param(::Val{N}) where {N} - (N isa Integer && N >= 1) || throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) +function _standardize_fix_param(::Val{N}) where {N} + if N isa Integer + if N < 1 + throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) + end + return Int64(N) + elseif N isa Symbol + return N + else + throw(ArgumentError("Expected type parameter in `Fix` to be an integer or symbol, but got type=$(typeof(N))")) + end end function _validate_fix_args(::Val{N}, ::Val{M}) where {N,M} - N <= 1 || M >= N - 1 || throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + if N isa Integer && N > 1 && M < N - 1 + throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + end end -function _validate_fix_kwargs(f_k, kws) - isempty(kws) || isempty(f_k) || isdisjoint(keys(kws), keys(f_k)) || - throw(ArgumentError("found duplicate keyword argument(s) passed to `Fix{N}`")) +function _validate_fix_kwargs(::Val{N}; kws...) where {N} + if N isa Symbol && N in keys(kws) + throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) + end end """ @@ -1209,7 +1226,7 @@ A type representing a partially-applied version of the function See also [`Fix2`](@ref Base.Fix2) and [`Fix`](@ref Base.Fix). """ -const Fix1{F,T} = Fix{1,F,T,typeof((;))} +const Fix1{F,T} = Fix{1,F,T} Fix1(f, x) = Fix{1}(f, x) """ @@ -1221,7 +1238,7 @@ A type representing a partially-applied version of the function See also [`Fix`](@ref Base.Fix). """ -const Fix2{F,T} = Fix{2,F,T,typeof((;))} +const Fix2{F,T} = Fix{2,F,T} Fix2(f, x) = Fix{2}(f, x) diff --git a/test/functional.jl b/test/functional.jl index f1de920e990ab..4269d9a002cdd 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -357,10 +357,6 @@ end sum1 = Base.Fix(sum; dims=1) x = ones(3, 2) sum1(x) == [3.0 3.0] - - # Now, with arg and kwargs - cat1 = Base.Fix{1}(cat, ones(2); dims=1) - cat1(ones(3)) == ones(5) end @testset "Dummy-proofing" begin @test_throws ArgumentError Base.Fix{0}(>, 1) From 1b5ffc107ade15ef052662382ba5d87d74c12636 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 20 Jun 2024 10:55:37 +0100 Subject: [PATCH 31/78] remove redundant `Fix1/Fix2` methods --- base/operators.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index e39043e149c88..bd9a29a5f8b1a 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1227,7 +1227,6 @@ A type representing a partially-applied version of the function See also [`Fix2`](@ref Base.Fix2) and [`Fix`](@ref Base.Fix). """ const Fix1{F,T} = Fix{1,F,T} -Fix1(f, x) = Fix{1}(f, x) """ Fix2(f, x) @@ -1239,7 +1238,6 @@ A type representing a partially-applied version of the function See also [`Fix`](@ref Base.Fix). """ const Fix2{F,T} = Fix{2,F,T} -Fix2(f, x) = Fix{2}(f, x) """ From 6d92c1a9aed5da89e3776747006a94ed63a0f628 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:16:22 +0100 Subject: [PATCH 32/78] add note about nested `Fix` --- base/operators.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index bd9a29a5f8b1a..648e0f507cd6c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1161,6 +1161,11 @@ In other words, `Fix{3}(f, x)` behaves similarly to You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keyword argument. You can also write this as `Fix{:a}(g, 2)`. + +!!! note + Nesting multiple `Fix`es is discouraged as it is ambiguous and sometimes + counterintuitive. For example, `Fix{1}(Fix{2}(f, 4), 4)` fixes the first and second arg, + while `Fix{2}(Fix{1}(f, 4), 4)` fixes the first and third arg. """ struct Fix{N,F,T} <: Function f::F From bc053cf2f0dc43769a7689a4e964a077c9b67e4f Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:17:26 +0100 Subject: [PATCH 33/78] amend `Fix` validation to not convert to `Int64` --- base/operators.jl | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 648e0f507cd6c..990cd4239b974 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1172,8 +1172,8 @@ struct Fix{N,F,T} <: Function x::T function Fix{N}(f::Union{F,Type{F}}, x) where {N,F} - _N = _standardize_fix_param(Val(N)) - new{_N,_stable_typeof(f),_stable_typeof(x)}(f, x) + _validate_fix_param(Val(N)) + new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end function Fix(f::Union{F,Type{F}}; kws...) where {F} @@ -1199,16 +1199,11 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} end end -function _standardize_fix_param(::Val{N}) where {N} - if N isa Integer - if N < 1 - throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) - end - return Int64(N) - elseif N isa Symbol - return N - else - throw(ArgumentError("Expected type parameter in `Fix` to be an integer or symbol, but got type=$(typeof(N))")) +function _validate_fix_param(::Val{N}) where {N} + if N isa Int64 + N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) + elseif !(N isa Symbol) + throw(ArgumentError("Expected type parameter in `Fix` to be `Int64` or `Symbol`, but got type=$(typeof(N))")) end end function _validate_fix_args(::Val{N}, ::Val{M}) where {N,M} From 3c19ecfa7017788df0cadcd280bfb340aa295a42 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:17:57 +0100 Subject: [PATCH 34/78] improve `Fix` readability --- base/operators.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 990cd4239b974..d2cd6dbaab6cd 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1177,9 +1177,7 @@ struct Fix{N,F,T} <: Function end end function Fix(f::Union{F,Type{F}}; kws...) where {F} - if length(kws) != 1 - throw(ArgumentError("`Fix` expects exactly one argument or keyword argument")) - end + length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument")) Fix{only(keys(kws))}(f, only(values(kws))) end From 3374b8fb4908513460f41ddfeb2f57af2659d2b9 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:18:31 +0100 Subject: [PATCH 35/78] rearrange branches in `Fix` for robust errors --- base/operators.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index d2cd6dbaab6cd..d7eed5dbfb663 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1185,15 +1185,15 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} @inline _validate_fix_args(Val(N), Val(M)) _validate_fix_kwargs(Val(N); kws...) - if N isa Integer + if N isa Symbol + f_kws = NamedTuple{(N,)}((f.x,)) + return f.f(args...; f_kws..., kws...) + else # Integer if N > 1 return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) else # N == 1 return f.f(f.x, args...; kws...) end - else # N isa Symbol - f_kws = NamedTuple{(N,)}((f.x,)) - return f.f(args...; f_kws..., kws...) end end From 8d9c690134e15b9f11b455604c2e7ae689dd78d8 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:19:05 +0100 Subject: [PATCH 36/78] rewrite `Fix1`/`Fix2` docstrings to be aliases --- base/operators.jl | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index d7eed5dbfb663..c1019be454079 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1216,24 +1216,12 @@ function _validate_fix_kwargs(::Val{N}; kws...) where {N} end """ - Fix1(f, x) - -A type representing a partially-applied version of the function -`f`, with the first argument fixed to the value "x". In other words, -`Fix1(f, x)` behaves similarly to `y->f(x, y)` for a 2-argument function `f`. - -See also [`Fix2`](@ref Base.Fix2) and [`Fix`](@ref Base.Fix). +Alias for `Fix{1}`. See [`Fix`](@ref Base.Fix). """ const Fix1{F,T} = Fix{1,F,T} """ - Fix2(f, x) - -A type representing a partially-applied version of the function -`f`, with the second argument fixed to the value "x". In other words, -`Fix2(f, x)` behaves similarly to `y->f(y, x)` for a 2-argument function `f`. - -See also [`Fix`](@ref Base.Fix). +Alias for `Fix{2}`. See [`Fix`](@ref Base.Fix). """ const Fix2{F,T} = Fix{2,F,T} From 9a4926e6c5f879c0e2f9beadebd806bd6c03b051 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 5 Jul 2024 00:33:07 +0900 Subject: [PATCH 37/78] Apply suggestions from code review Co-authored-by: Lilith Orion Hafner --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index c1019be454079..5362f64c43d1c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1171,12 +1171,12 @@ struct Fix{N,F,T} <: Function f::F x::T - function Fix{N}(f::Union{F,Type{F}}, x) where {N,F} + function Fix{N}(f::F, x) where {N,F} _validate_fix_param(Val(N)) new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end -function Fix(f::Union{F,Type{F}}; kws...) where {F} +function Fix(f::F; kws...) where {F} length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument")) Fix{only(keys(kws))}(f, only(values(kws))) end From aa08daf3ea734887386c0a2ce450cc05f877a6a5 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:40:10 +0100 Subject: [PATCH 38/78] single branch for `Fix{n::Int}` --- base/operators.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 5362f64c43d1c..415ae19338231 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1189,11 +1189,7 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Integer - if N > 1 - return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) - else # N == 1 - return f.f(f.x, args...; kws...) - end + return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end end From e541a51ac676393b29cd976412fa78ff9b5c37a7 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 5 Jul 2024 00:43:54 +0900 Subject: [PATCH 39/78] Apply suggestions from code review Co-authored-by: Lilith Orion Hafner --- base/operators.jl | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 415ae19338231..75e4fbbe5639b 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1183,8 +1183,11 @@ end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} @inline - _validate_fix_args(Val(N), Val(M)) - _validate_fix_kwargs(Val(N); kws...) + if N isa Int && M < N-1 + throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + elseif N isa Symbol && N in keys(kws) + throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) + end if N isa Symbol f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) @@ -1200,16 +1203,6 @@ function _validate_fix_param(::Val{N}) where {N} throw(ArgumentError("Expected type parameter in `Fix` to be `Int64` or `Symbol`, but got type=$(typeof(N))")) end end -function _validate_fix_args(::Val{N}, ::Val{M}) where {N,M} - if N isa Integer && N > 1 && M < N - 1 - throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) - end -end -function _validate_fix_kwargs(::Val{N}; kws...) where {N} - if N isa Symbol && N in keys(kws) - throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) - end -end """ Alias for `Fix{1}`. See [`Fix`](@ref Base.Fix). From 751e964dfbf69b2616440887a6d69fa8f193131f Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:49:35 +0100 Subject: [PATCH 40/78] one-line validations --- base/operators.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 75e4fbbe5639b..ee7dc1acbe47f 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1183,11 +1183,9 @@ end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} @inline - if N isa Int && M < N-1 - throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) - elseif N isa Symbol && N in keys(kws) - throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) - end + N isa Symbol && N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) + N isa Int && M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + if N isa Symbol f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) From 669f753133b0a81766bfeed54f4e42c486c05db2 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:49:46 +0100 Subject: [PATCH 41/78] remove `@inline` as not needed --- base/operators.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index ee7dc1acbe47f..2a0d109cd0ef0 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1182,7 +1182,6 @@ function Fix(f::F; kws...) where {F} end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} - @inline N isa Symbol && N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) N isa Int && M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) From 5694840c4210a301f328731f016e9a360ff15c46 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:51:13 +0100 Subject: [PATCH 42/78] add `Fix` to public --- base/public.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/public.jl b/base/public.jl index 70e96ce2f5201..99026fe5c2d91 100644 --- a/base/public.jl +++ b/base/public.jl @@ -13,6 +13,7 @@ public AsyncCondition, CodeUnits, Event, + Fix, Fix1, Fix2, Generator, From 87f5575d43bc593d92b5b287f15a9ca26fbfa8dc Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:53:36 +0100 Subject: [PATCH 43/78] update comments --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 2a0d109cd0ef0..346e7b209f17c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1188,13 +1188,13 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} if N isa Symbol f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) - else # Integer + else # Int return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end end function _validate_fix_param(::Val{N}) where {N} - if N isa Int64 + if N isa Int N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) elseif !(N isa Symbol) throw(ArgumentError("Expected type parameter in `Fix` to be `Int64` or `Symbol`, but got type=$(typeof(N))")) From 79dcd6df868287d75e58de96684489dd24ec4a8e Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 16:54:49 +0100 Subject: [PATCH 44/78] avoid redundant `Val` --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 346e7b209f17c..80f9d41083e96 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1172,7 +1172,7 @@ struct Fix{N,F,T} <: Function x::T function Fix{N}(f::F, x) where {N,F} - _validate_fix_param(Val(N)) + _validate_fix_param(N) new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end @@ -1193,7 +1193,7 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} end end -function _validate_fix_param(::Val{N}) where {N} +function _validate_fix_param(N) if N isa Int N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) elseif !(N isa Symbol) From f3d5c5cc57b78c8dd2272186f5ccbd4c62311279 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 17:00:39 +0100 Subject: [PATCH 45/78] reduce lines of `_validate_fix_param` --- base/operators.jl | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 80f9d41083e96..51a423a934b58 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1194,11 +1194,8 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} end function _validate_fix_param(N) - if N isa Int - N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) - elseif !(N isa Symbol) - throw(ArgumentError("Expected type parameter in `Fix` to be `Int64` or `Symbol`, but got type=$(typeof(N))")) - end + N isa Int && N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) + !(N isa Union{Int,Symbol}) && throw(ArgumentError("Expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=$(typeof(N))")) end """ From c8bc1abdd47f3ca908d01c10f66e9098e15538ca Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 17:02:03 +0100 Subject: [PATCH 46/78] remove old empty `Fix` test --- test/functional.jl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/functional.jl b/test/functional.jl index 4269d9a002cdd..119cac6bbe62b 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -296,12 +296,6 @@ end @test fixed_f3(1, 2) == 1 + 2 * 3 end end - @testset "allowed empty function" begin - let f = () -> 1 - fixed_f = Fix(f) - @test fixed_f() == 1 - end - end @testset "Helpful errors" begin let g = (x, y) -> x - y # Test minimum N From e9c566bc6ed1aff570c09debb6a5ed8cacde4272 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 17:03:57 +0100 Subject: [PATCH 47/78] update `Fix` tests with latest changes --- base/operators.jl | 2 +- test/functional.jl | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 51a423a934b58..7fefaa85b0b6f 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1195,7 +1195,7 @@ end function _validate_fix_param(N) N isa Int && N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) - !(N isa Union{Int,Symbol}) && throw(ArgumentError("Expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=$(typeof(N))")) + !(N isa Union{Int,Symbol}) && throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=$(typeof(N))")) end """ diff --git a/test/functional.jl b/test/functional.jl index 119cac6bbe62b..a73099e645247 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -348,24 +348,21 @@ end @test g3("") == "123" end @testset "With kwargs" begin - sum1 = Base.Fix(sum; dims=1) + sum1 = Fix(sum; dims=1) x = ones(3, 2) sum1(x) == [3.0 3.0] end @testset "Dummy-proofing" begin - @test_throws ArgumentError Base.Fix{0}(>, 1) - @test_throws "expected `N` in `Fix{N}` to be integer greater than 0" Base.Fix{0}(>, 1) + @test_throws ArgumentError Fix{0}(>, 1) + @test_throws "expected `N` in `Fix{N}` to be integer greater than 0" Fix{0}(>, 1) - @test_throws ArgumentError Base.Fix{0.5}(>, 1) - @test_throws "expected `N` in `Fix{N}` to be integer greater than 0" Base.Fix{0.5}(>, 1) + @test_throws ArgumentError Fix{0.5}(>, 1) + @test_throws "expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=Float64" Fix{0.5}(>, 1) - _get_fix_n(::Fix{N}) where {N} = N - f = Fix{UInt64(1)}(>, 1) - # Will automatically convert - @test _get_fix_n(f) isa Int64 + @test_throws ArgumentError Fix{UInt64(1)}(>, 1) # duplicate keywords - sum1 = Base.Fix(sum; dims=1) + sum1 = Fix(sum; dims=1) x = ones(3, 2) @test sum1(x) == [3.0 3.0] @test_throws ArgumentError sum1(x; dims=2) From 9b1d74949f3dcce4cba453f7e5b838547e57e6cd Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 17:09:32 +0100 Subject: [PATCH 48/78] update `Fix` test with new error message --- test/functional.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional.jl b/test/functional.jl index a73099e645247..09fd104552edb 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -366,7 +366,7 @@ end x = ones(3, 2) @test sum1(x) == [3.0 3.0] @test_throws ArgumentError sum1(x; dims=2) - @test_throws "found duplicate keyword argument(s) passed to `Fix{N}`" sum1(x; dims=2) + @test_throws "found duplicate keyword argument passed to `Fix{N}`" sum1(x; dims=2) end end end From 47a49bf2cb752d92a161fae08f5dc2230c3d6085 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 17:11:30 +0100 Subject: [PATCH 49/78] reduce branches --- base/operators.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 7fefaa85b0b6f..4eb56e7024d25 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1182,13 +1182,12 @@ function Fix(f::F; kws...) where {F} end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} - N isa Symbol && N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) - N isa Int && M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) - if N isa Symbol + N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Int + M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end end From 06aab6fd6f4a82be8d4441f239f53816c969f81b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 18:55:23 +0100 Subject: [PATCH 50/78] inline `_validate_fix_param` --- base/operators.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 4eb56e7024d25..1afd4c52d75ed 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1172,7 +1172,11 @@ struct Fix{N,F,T} <: Function x::T function Fix{N}(f::F, x) where {N,F} - _validate_fix_param(N) + if N isa Int && N < 1 + throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) + elseif !(N isa Union{Int,Symbol}) + throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=$(typeof(N))")) + end new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end @@ -1192,11 +1196,6 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} end end -function _validate_fix_param(N) - N isa Int && N < 1 && throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) - !(N isa Union{Int,Symbol}) && throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=$(typeof(N))")) -end - """ Alias for `Fix{1}`. See [`Fix`](@ref Base.Fix). """ From 602807439b51ac267550b514dca9d84f9ae64837 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 5 Jul 2024 04:58:16 +0900 Subject: [PATCH 51/78] Apply suggestions from code review Co-authored-by: Lilith Orion Hafner --- base/operators.jl | 2 +- test/functional.jl | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 1afd4c52d75ed..b45a40d91e279 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1187,7 +1187,7 @@ end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} if N isa Symbol - N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix{N}`")) + N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix`")) f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Int diff --git a/test/functional.jl b/test/functional.jl index 09fd104552edb..2cc704909ca9a 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -353,11 +353,9 @@ end sum1(x) == [3.0 3.0] end @testset "Dummy-proofing" begin - @test_throws ArgumentError Fix{0}(>, 1) - @test_throws "expected `N` in `Fix{N}` to be integer greater than 0" Fix{0}(>, 1) + @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0") Fix{0}(>, 1) - @test_throws ArgumentError Fix{0.5}(>, 1) - @test_throws "expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=Float64" Fix{0.5}(>, 1) + @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=Float64") Fix{0.5}(>, 1) @test_throws ArgumentError Fix{UInt64(1)}(>, 1) @@ -365,8 +363,7 @@ end sum1 = Fix(sum; dims=1) x = ones(3, 2) @test sum1(x) == [3.0 3.0] - @test_throws ArgumentError sum1(x; dims=2) - @test_throws "found duplicate keyword argument passed to `Fix{N}`" sum1(x; dims=2) + @test_throws ArgumentError("found duplicate keyword argument passed to `Fix`") sum1(x; dims=2) end end end From f2980d445a026d18c38650aecd3e62974038059d Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 5 Jul 2024 05:08:32 +0900 Subject: [PATCH 52/78] Update base/operators.jl Co-authored-by: Lilith Orion Hafner --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index b45a40d91e279..f743b4be0926e 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1151,7 +1151,7 @@ julia> filter(!isletter, str) """ Fix{n}(f, x) Fix{kw}(f, x) - Fix(f; [kw]=x) + Fix(f; [kw=]x) A type representing a partially-applied version of a function `f`, with the argument "x" fixed at argument `n::Int` or keyword `kw::Symbol`. From 5146e1eadec2e9cc2bc7335306ffa22a15e3e920 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 21:11:57 +0100 Subject: [PATCH 53/78] clean up more redundancies in tests --- test/functional.jl | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/functional.jl b/test/functional.jl index 2cc704909ca9a..849c23103556c 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -308,11 +308,7 @@ end # One over fixed_g3 = Fix{3}(g, 100) - @test_throws ArgumentError fixed_g3(1) - @test_throws( - "expected at least 2 arguments to a `Fix` function with `N=3`", - fixed_g3(1) - ) + @test_throws ArgumentError("expected at least 2 arguments to a `Fix` function with `N=3`") fixed_g3(1) end end @testset "Type Stability and Inference" begin @@ -357,7 +353,7 @@ end @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=Float64") Fix{0.5}(>, 1) - @test_throws ArgumentError Fix{UInt64(1)}(>, 1) + @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=UInt64") Fix{UInt64(1)}(>, 1) # duplicate keywords sum1 = Fix(sum; dims=1) From cec16756472d80dc1545238ca16ff6968faa0ca5 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 21:12:26 +0100 Subject: [PATCH 54/78] clean up more redundancies in tests --- test/functional.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/functional.jl b/test/functional.jl index 849c23103556c..a1d382224384a 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -350,9 +350,7 @@ end end @testset "Dummy-proofing" begin @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0") Fix{0}(>, 1) - @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=Float64") Fix{0.5}(>, 1) - @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=UInt64") Fix{UInt64(1)}(>, 1) # duplicate keywords From 09ee6ece0fe9ad5d2cfc5769120d51cb24a319dd Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 4 Jul 2024 21:20:52 +0100 Subject: [PATCH 55/78] modify REPL test with new Fix docstring --- stdlib/REPL/test/repl.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 2d07a19c65cb9..705b265691718 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1215,9 +1215,9 @@ global some_undef_global @test occursin("does not exist", sprint(show, help_result(".."))) # test that helpmode is sensitive to contextual module @test occursin("No documentation found", sprint(show, help_result("Fix2", Main))) -@test occursin("A type representing a partially-applied version", # exact string may change +@test occursin("Alias for `Fix{2}`. See [`Fix`](@ref Base.Fix).", # exact string may change sprint(show, help_result("Base.Fix2", Main))) -@test occursin("A type representing a partially-applied version", # exact string may change +@test occursin("Alias for `Fix{2}`. See [`Fix`](@ref Base.Fix).", # exact string may change sprint(show, help_result("Fix2", Base))) From 42a1e3ea71edccc6e701ac67fb6468eb46fc865c Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 5 Jul 2024 13:15:51 +0100 Subject: [PATCH 56/78] add special cases for constant propagation --- base/operators.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index f743b4be0926e..6fdae1bfe6306 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1196,6 +1196,10 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} end end +# Special cases for improved constant propagation +(f::Fix{1})(arg; kws...) = f.f(f.x, arg; kws...) +(f::Fix{2})(arg; kws...) = f.f(arg, f.x; kws...) + """ Alias for `Fix{1}`. See [`Fix`](@ref Base.Fix). """ From bd51d2ec3990e6b812d8c5df616fd8fdf2d048ac Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 5 Jul 2024 14:41:59 +0100 Subject: [PATCH 57/78] soften wording of nested `Fix` note --- base/operators.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 6fdae1bfe6306..d518909a05289 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1163,9 +1163,10 @@ similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keywo You can also write this as `Fix{:a}(g, 2)`. !!! note - Nesting multiple `Fix`es is discouraged as it is ambiguous and sometimes - counterintuitive. For example, `Fix{1}(Fix{2}(f, 4), 4)` fixes the first and second arg, - while `Fix{2}(Fix{1}(f, 4), 4)` fixes the first and third arg. + When nesting multiple `Fix`, note that the `N` in `Fix{N}` is _relative_ to the current + available arguments, rather than an absolute ordering on the target function. For example, + `Fix{1}(Fix{2}(f, 4), 4)` fixes the first and second arg, while `Fix{2}(Fix{1}(f, 4), 4)` + fixes the first and third arg. """ struct Fix{N,F,T} <: Function f::F From 9d8e9abc326704b997f92ff7062445b8c2d9ade7 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Sat, 6 Jul 2024 01:16:21 +0900 Subject: [PATCH 58/78] Apply suggestions from code review Co-authored-by: Lilith Orion Hafner --- base/operators.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index d518909a05289..4fcb8da8eb2e5 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1150,8 +1150,8 @@ julia> filter(!isletter, str) """ Fix{n}(f, x) - Fix{kw}(f, x) - Fix(f; [kw=]x) + Fix{:kw}(f, x) + Fix(f; kw=x) A type representing a partially-applied version of a function `f`, with the argument "x" fixed at argument `n::Int` or keyword `kw::Symbol`. From 717f39b017e6e4a223a23ab1c27785ba84fb2e85 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Sun, 7 Jul 2024 08:25:47 +0900 Subject: [PATCH 59/78] Apply suggestions from code review Co-authored-by: Lilith Orion Hafner Co-authored-by: Alexander Plavin --- base/operators.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 4fcb8da8eb2e5..30a258fdb1c45 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1156,7 +1156,7 @@ julia> filter(!isletter, str) A type representing a partially-applied version of a function `f`, with the argument "x" fixed at argument `n::Int` or keyword `kw::Symbol`. In other words, `Fix{3}(f, x)` behaves similarly to -`(y1, y2, y3) -> f(y1, y2, x, y3)` for the 4-argument function `f`. +`(y1, y2, y3...) -> f(y1, y2, x, y3...)`. You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keyword argument. @@ -1174,25 +1174,25 @@ struct Fix{N,F,T} <: Function function Fix{N}(f::F, x) where {N,F} if N isa Int && N < 1 - throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0")) + throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, got $N")) elseif !(N isa Union{Int,Symbol}) - throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=$(typeof(N))")) + throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got $N::$(typeof(N))")) end new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end function Fix(f::F; kws...) where {F} - length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument")) + length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument, got keywords $(keys(kws))")) Fix{only(keys(kws))}(f, only(values(kws))) end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} if N isa Symbol - N in keys(kws) && throw(ArgumentError("found duplicate keyword argument passed to `Fix`")) + N in keys(kws) && throw(ArgumentError("found duplicate keyword argument $N passed to a `Fix` function")) f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Int - M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`")) + M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`, got $M")) return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end end From 3f2b11609eff335636d35d3476a7a099cab0964e Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 7 Jul 2024 00:33:07 +0100 Subject: [PATCH 60/78] test for Fix: update tests to new error message; add zero-arg test --- base/operators.jl | 4 ++-- test/functional.jl | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 30a258fdb1c45..c3c79d41f97fc 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1176,7 +1176,7 @@ struct Fix{N,F,T} <: Function if N isa Int && N < 1 throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, got $N")) elseif !(N isa Union{Int,Symbol}) - throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got $N::$(typeof(N))")) + throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `$N::$(typeof(N))`")) end new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end @@ -1188,7 +1188,7 @@ end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} if N isa Symbol - N in keys(kws) && throw(ArgumentError("found duplicate keyword argument $N passed to a `Fix` function")) + N in keys(kws) && throw(ArgumentError("found duplicate keyword argument `$N` passed to a `Fix` function")) f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Int diff --git a/test/functional.jl b/test/functional.jl index a1d382224384a..93344c40bca96 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -308,7 +308,7 @@ end # One over fixed_g3 = Fix{3}(g, 100) - @test_throws ArgumentError("expected at least 2 arguments to a `Fix` function with `N=3`") fixed_g3(1) + @test_throws ArgumentError("expected at least 2 arguments to a `Fix` function with `N=3`, got 1") fixed_g3(1) end end @testset "Type Stability and Inference" begin @@ -348,16 +348,20 @@ end x = ones(3, 2) sum1(x) == [3.0 3.0] end + @testset "Zero arguments" begin + f = Fix{1}(x -> x, 'a') + @test f() == 'a' + end @testset "Dummy-proofing" begin - @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0") Fix{0}(>, 1) - @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=Float64") Fix{0.5}(>, 1) - @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got type=UInt64") Fix{UInt64(1)}(>, 1) + @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, got 0") Fix{0}(>, 1) + @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `0.5::Float64`") Fix{0.5}(>, 1) + @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) # duplicate keywords sum1 = Fix(sum; dims=1) x = ones(3, 2) @test sum1(x) == [3.0 3.0] - @test_throws ArgumentError("found duplicate keyword argument passed to `Fix`") sum1(x; dims=2) + @test_throws ArgumentError("found duplicate keyword argument `dims` passed to a `Fix` function") sum1(x; dims=2) end end end From 316db38fc43746d958d8dde20c0ff5c731d033c0 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 7 Jul 2024 00:34:11 +0100 Subject: [PATCH 61/78] grammar in error message --- base/operators.jl | 2 +- test/functional.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index c3c79d41f97fc..acee15b1bf5d6 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1174,7 +1174,7 @@ struct Fix{N,F,T} <: Function function Fix{N}(f::F, x) where {N,F} if N isa Int && N < 1 - throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, got $N")) + throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, but got $N")) elseif !(N isa Union{Int,Symbol}) throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `$N::$(typeof(N))`")) end diff --git a/test/functional.jl b/test/functional.jl index 93344c40bca96..3ccdb7b27e3f4 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -353,7 +353,7 @@ end @test f() == 'a' end @testset "Dummy-proofing" begin - @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, got 0") Fix{0}(>, 1) + @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, but got 0") Fix{0}(>, 1) @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `0.5::Float64`") Fix{0.5}(>, 1) @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) From 04c247cbe1f11d74966e46bf501642a17f9cde7e Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 7 Jul 2024 00:35:49 +0100 Subject: [PATCH 62/78] more grammar tweaks in error message --- base/operators.jl | 4 ++-- test/functional.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index acee15b1bf5d6..2fd7604e2f2c7 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1182,7 +1182,7 @@ struct Fix{N,F,T} <: Function end end function Fix(f::F; kws...) where {F} - length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument, got keywords $(keys(kws))")) + length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument, but got keywords $(keys(kws))")) Fix{only(keys(kws))}(f, only(values(kws))) end @@ -1192,7 +1192,7 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Int - M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`, got $M")) + M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`, bug got $M")) return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end end diff --git a/test/functional.jl b/test/functional.jl index 3ccdb7b27e3f4..50b3ccbb812c8 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -308,7 +308,7 @@ end # One over fixed_g3 = Fix{3}(g, 100) - @test_throws ArgumentError("expected at least 2 arguments to a `Fix` function with `N=3`, got 1") fixed_g3(1) + @test_throws ArgumentError("expected at least 2 arguments to a `Fix` function with `N=3`, but got 1") fixed_g3(1) end end @testset "Type Stability and Inference" begin From 1908c1f2fa0ae96372e9e353b24d5c7b7c421102 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Sun, 7 Jul 2024 00:39:14 +0100 Subject: [PATCH 63/78] test multiple kw error message --- base/operators.jl | 4 ++-- test/functional.jl | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 2fd7604e2f2c7..472d0caf83572 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1182,7 +1182,7 @@ struct Fix{N,F,T} <: Function end end function Fix(f::F; kws...) where {F} - length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument, but got keywords $(keys(kws))")) + length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument, but got keywords `$(keys(kws))`")) Fix{only(keys(kws))}(f, only(values(kws))) end @@ -1192,7 +1192,7 @@ function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} f_kws = NamedTuple{(N,)}((f.x,)) return f.f(args...; f_kws..., kws...) else # Int - M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`, bug got $M")) + M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`, but got $M")) return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end end diff --git a/test/functional.jl b/test/functional.jl index 50b3ccbb812c8..38d1682a922fd 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -362,6 +362,9 @@ end x = ones(3, 2) @test sum1(x) == [3.0 3.0] @test_throws ArgumentError("found duplicate keyword argument `dims` passed to a `Fix` function") sum1(x; dims=2) + + # Trying to fix multiple keywords + @test_throws ArgumentError("`Fix` expects exactly one argument or keyword argument, but got keywords `(:kw1, :kw2)`") Fix((args...; kwargs...) -> 1; kw1=1, kw2=2) end end end From d514e465fc1d90ea906cb6c16fb1d65760ebc0d0 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 26 Jul 2024 07:55:32 +0900 Subject: [PATCH 64/78] Update base/operators.jl Co-authored-by: Neven Sajko --- base/operators.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index 472d0caf83572..156b15923773c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1161,6 +1161,8 @@ In other words, `Fix{3}(f, x)` behaves similarly to You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keyword argument. You can also write this as `Fix{:a}(g, 2)`. +!!! compat "Julia 1.12" + Requires Julia 1.12 or later. !!! note When nesting multiple `Fix`, note that the `N` in `Fix{N}` is _relative_ to the current From ded1bfb5ede0e019aa8c6fbf9c4cc3edc3447bc4 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 26 Jul 2024 07:56:15 +0900 Subject: [PATCH 65/78] docs: space out compat statement --- base/operators.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/operators.jl b/base/operators.jl index 156b15923773c..7683dffc3c4ec 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1161,6 +1161,7 @@ In other words, `Fix{3}(f, x)` behaves similarly to You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keyword argument. You can also write this as `Fix{:a}(g, 2)`. + !!! compat "Julia 1.12" Requires Julia 1.12 or later. From 2e9a91c0b1f6da02fd1255ec02e8167cb65074b4 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 26 Jul 2024 20:38:38 +0100 Subject: [PATCH 66/78] docs: describe `Fix` in NEWS --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index a056b85de2499..8a9b13928bba7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -61,6 +61,7 @@ New library functions * The new `isfull(c::Channel)` function can be used to check if `put!(c, some_value)` will block. ([#53159]) * `waitany(tasks; throw=false)` and `waitall(tasks; failfast=false, throw=false)` which wait multiple tasks at once ([#53341]). * `uuid7()` creates an RFC 9652 compliant UUID with version 7 ([#54834]). +* The new `Fix` type is a generalization of `Fix1/Fix2` for fixing an argument or keyword argument ([#54653]). New library features -------------------- From 08d774b7ba55166efb82604293fd4ee583442919 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 26 Jul 2024 20:46:09 +0100 Subject: [PATCH 67/78] docs: tweak docstring for Fix --- base/operators.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 7683dffc3c4ec..6b86c97689dd3 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1154,19 +1154,18 @@ julia> filter(!isletter, str) Fix(f; kw=x) A type representing a partially-applied version of a function `f`, with the argument -"x" fixed at argument `n::Int` or keyword `kw::Symbol`. +`x` fixed at argument `n::Int` or keyword `kw::Symbol`. In other words, `Fix{3}(f, x)` behaves similarly to -`(y1, y2, y3...) -> f(y1, y2, x, y3...)`. +`(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves -similarly to `x -> g(x; a=2)` for a function `g` with one argument and one keyword argument. -You can also write this as `Fix{:a}(g, 2)`. +similarly to `(x...; kws...) -> g(x...; a=2, kws...)`. You can also write this as `Fix{:a}(g, 2)`. !!! compat "Julia 1.12" Requires Julia 1.12 or later. !!! note - When nesting multiple `Fix`, note that the `N` in `Fix{N}` is _relative_ to the current + When nesting multiple `Fix`, note that the `n` in `Fix{n}` is _relative_ to the current available arguments, rather than an absolute ordering on the target function. For example, `Fix{1}(Fix{2}(f, 4), 4)` fixes the first and second arg, while `Fix{2}(Fix{1}(f, 4), 4)` fixes the first and third arg. From 1a107b0f7bd6790476f3a81404b4f1eb540e3c13 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 26 Jul 2024 20:47:02 +0100 Subject: [PATCH 68/78] docs: formatting docstring for Fix --- base/operators.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 6b86c97689dd3..04c1ca3e2e6bb 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1154,9 +1154,8 @@ julia> filter(!isletter, str) Fix(f; kw=x) A type representing a partially-applied version of a function `f`, with the argument -`x` fixed at argument `n::Int` or keyword `kw::Symbol`. -In other words, `Fix{3}(f, x)` behaves similarly to -`(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. +`x` fixed at position `n::Int` or keyword `kw::Symbol`. In other words, `Fix{3}(f, x)` +behaves similarly to `(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves similarly to `(x...; kws...) -> g(x...; a=2, kws...)`. You can also write this as `Fix{:a}(g, 2)`. From 6bc91a27c3cca77569ef0d2080fd78f45005b8fa Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 26 Jul 2024 20:48:10 +0100 Subject: [PATCH 69/78] nitpick docstring grammar --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 04c1ca3e2e6bb..0384c87ebfac3 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1158,7 +1158,7 @@ A type representing a partially-applied version of a function `f`, with the argu behaves similarly to `(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves -similarly to `(x...; kws...) -> g(x...; a=2, kws...)`. You can also write this as `Fix{:a}(g, 2)`. +similarly to `(y...; kws...) -> g(y...; a=2, kws...)`. You can also write this as `Fix{:a}(g, 2)`. !!! compat "Julia 1.12" Requires Julia 1.12 or later. From 3e19e9701a4feac0b49db265a0a707007734eaf1 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Fri, 26 Jul 2024 20:50:39 +0100 Subject: [PATCH 70/78] normalize `compat` statement to other docstrings --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 0384c87ebfac3..be7d41a37fe35 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1161,7 +1161,7 @@ You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behav similarly to `(y...; kws...) -> g(y...; a=2, kws...)`. You can also write this as `Fix{:a}(g, 2)`. !!! compat "Julia 1.12" - Requires Julia 1.12 or later. + This functionality requires at least Julia 1.12. !!! note When nesting multiple `Fix`, note that the `n` in `Fix{n}` is _relative_ to the current From ff197b8fd57fe9344e02d02c54318dd3c7369ed5 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 16:07:18 +0100 Subject: [PATCH 71/78] feat: remove single keyword version of `Fix` --- base/operators.jl | 35 ++++++++++------------------------- test/functional.jl | 20 +++----------------- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index be7d41a37fe35..90c755074275b 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1149,22 +1149,17 @@ julia> filter(!isletter, str) !(f::ComposedFunction{typeof(!)}) = f.inner #allows !!f === f """ - Fix{n}(f, x) - Fix{:kw}(f, x) - Fix(f; kw=x) + Fix{N}(f, x) A type representing a partially-applied version of a function `f`, with the argument -`x` fixed at position `n::Int` or keyword `kw::Symbol`. In other words, `Fix{3}(f, x)` -behaves similarly to `(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. - -You may also use this to fix keyword arguments. For example, `Fix(g; a=2)` behaves -similarly to `(y...; kws...) -> g(y...; a=2, kws...)`. You can also write this as `Fix{:a}(g, 2)`. +`x` fixed at position `N::Int`. In other words, `Fix{3}(f, x)` behaves similarly to +`(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. !!! compat "Julia 1.12" - This functionality requires at least Julia 1.12. + The general form of `Fix1` and `Fix2` to `Fix{N}` requires at least Julia 1.12. !!! note - When nesting multiple `Fix`, note that the `n` in `Fix{n}` is _relative_ to the current + When nesting multiple `Fix`, note that the `N` in `Fix{N}` is _relative_ to the current available arguments, rather than an absolute ordering on the target function. For example, `Fix{1}(Fix{2}(f, 4), 4)` fixes the first and second arg, while `Fix{2}(Fix{1}(f, 4), 4)` fixes the first and third arg. @@ -1174,28 +1169,18 @@ struct Fix{N,F,T} <: Function x::T function Fix{N}(f::F, x) where {N,F} - if N isa Int && N < 1 + if !(N isa Int) + throw(ArgumentError("expected type parameter in `Fix` to be `Int`, but got `$N::$(typeof(N))`")) + elseif N < 1 throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, but got $N")) - elseif !(N isa Union{Int,Symbol}) - throw(ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `$N::$(typeof(N))`")) end new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end -function Fix(f::F; kws...) where {F} - length(kws) != 1 && throw(ArgumentError("`Fix` expects exactly one argument or keyword argument, but got keywords `$(keys(kws))`")) - Fix{only(keys(kws))}(f, only(values(kws))) -end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} - if N isa Symbol - N in keys(kws) && throw(ArgumentError("found duplicate keyword argument `$N` passed to a `Fix` function")) - f_kws = NamedTuple{(N,)}((f.x,)) - return f.f(args...; f_kws..., kws...) - else # Int - M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to a `Fix` function with `N=$(N)`, but got $M")) - return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) - end + M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to `Fix{$(N)}`, but got $M")) + return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end # Special cases for improved constant propagation diff --git a/test/functional.jl b/test/functional.jl index 38d1682a922fd..9961b24fe8fa9 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -308,7 +308,7 @@ end # One over fixed_g3 = Fix{3}(g, 100) - @test_throws ArgumentError("expected at least 2 arguments to a `Fix` function with `N=3`, but got 1") fixed_g3(1) + @test_throws ArgumentError("expected at least 2 arguments to `Fix{3}`, but got 1") fixed_g3(1) end end @testset "Type Stability and Inference" begin @@ -343,28 +343,14 @@ end g3 = Fix{2}(g2, "3") @test g3("") == "123" end - @testset "With kwargs" begin - sum1 = Fix(sum; dims=1) - x = ones(3, 2) - sum1(x) == [3.0 3.0] - end @testset "Zero arguments" begin f = Fix{1}(x -> x, 'a') @test f() == 'a' end @testset "Dummy-proofing" begin @test_throws ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, but got 0") Fix{0}(>, 1) - @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `0.5::Float64`") Fix{0.5}(>, 1) - @test_throws ArgumentError("expected type parameter in `Fix` to be `Int` or `Symbol`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) - - # duplicate keywords - sum1 = Fix(sum; dims=1) - x = ones(3, 2) - @test sum1(x) == [3.0 3.0] - @test_throws ArgumentError("found duplicate keyword argument `dims` passed to a `Fix` function") sum1(x; dims=2) - - # Trying to fix multiple keywords - @test_throws ArgumentError("`Fix` expects exactly one argument or keyword argument, but got keywords `(:kw1, :kw2)`") Fix((args...; kwargs...) -> 1; kw1=1, kw2=2) + @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `0.5::Float64`") Fix{0.5}(>, 1) + @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) end end end From 9e813fa34b0ad1a407f5ab4ab91c54a072ff4b49 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 16:15:24 +0100 Subject: [PATCH 72/78] docs: back to old compat statement --- base/operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 90c755074275b..e58757c5e7bd8 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1156,7 +1156,7 @@ A type representing a partially-applied version of a function `f`, with the argu `(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. !!! compat "Julia 1.12" - The general form of `Fix1` and `Fix2` to `Fix{N}` requires at least Julia 1.12. + This functionality requires at least Julia 1.12. !!! note When nesting multiple `Fix`, note that the `N` in `Fix{N}` is _relative_ to the current From 04dfe0995f61cb89a5e985683e7ed156cc01db0d Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 16:15:46 +0100 Subject: [PATCH 73/78] refactor: `LazyString` versions of `Fix` --- base/operators.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index e58757c5e7bd8..5b1746dcbc79c 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1170,16 +1170,16 @@ struct Fix{N,F,T} <: Function function Fix{N}(f::F, x) where {N,F} if !(N isa Int) - throw(ArgumentError("expected type parameter in `Fix` to be `Int`, but got `$N::$(typeof(N))`")) + throw(ArgumentError(LazyString("expected type parameter in `Fix` to be `Int`, but got `", N, "::", typeof(N), "`"))) elseif N < 1 - throw(ArgumentError("expected `N` in `Fix{N}` to be integer greater than 0, but got $N")) + throw(ArgumentError(LazyString("expected `N` in `Fix{N}` to be integer greater than 0, but got ", N))) end new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end function (f::Fix{N})(args::Vararg{Any,M}; kws...) where {N,M} - M < N-1 && throw(ArgumentError("expected at least $(N-1) arguments to `Fix{$(N)}`, but got $M")) + M < N-1 && throw(ArgumentError(LazyString("expected at least ", N-1, " arguments to `Fix{", N, "}`, but got ", M))) return f.f(args[begin:begin+(N-2)]..., f.x, args[begin+(N-1):end]...; kws...) end From 6cac583799d505f56a0c8d2196486c4a66972a76 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 16:19:08 +0100 Subject: [PATCH 74/78] docs: tweak compat statement --- base/operators.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/operators.jl b/base/operators.jl index 5b1746dcbc79c..cbdb20a2bdaca 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1156,7 +1156,8 @@ A type representing a partially-applied version of a function `f`, with the argu `(y1, y2, y3...; kws...) -> f(y1, y2, x, y3...; kws...)`. !!! compat "Julia 1.12" - This functionality requires at least Julia 1.12. + This general functionality requires at least Julia 1.12, while `Fix1` and `Fix2` + are available earlier. !!! note When nesting multiple `Fix`, note that the `N` in `Fix{N}` is _relative_ to the current From ed64473cf4aa8bf7ac372dbb13722c1be12073b5 Mon Sep 17 00:00:00 2001 From: Miles Cranmer Date: Fri, 2 Aug 2024 00:25:46 +0900 Subject: [PATCH 75/78] docs: update NEWS.md with removed keyword arg functionality --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 85c3ca5db1590..e7f0d4d0e3299 100644 --- a/NEWS.md +++ b/NEWS.md @@ -72,7 +72,7 @@ New library functions * The new `isfull(c::Channel)` function can be used to check if `put!(c, some_value)` will block. ([#53159]) * `waitany(tasks; throw=false)` and `waitall(tasks; failfast=false, throw=false)` which wait multiple tasks at once ([#53341]). * `uuid7()` creates an RFC 9652 compliant UUID with version 7 ([#54834]). -* The new `Fix` type is a generalization of `Fix1/Fix2` for fixing an argument or keyword argument ([#54653]). +* The new `Fix` type is a generalization of `Fix1/Fix2` for fixing a single argument ([#54653]). New library features -------------------- From 4999c2f24e10605fdaeb2b4560baa4aa08cb80d8 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 17:38:27 +0100 Subject: [PATCH 76/78] fix: change in specialization from `_stable_typeof` --- base/operators.jl | 4 ++-- test/functional.jl | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index bee2949e4918f..3936925bbbb1b 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1170,13 +1170,13 @@ struct Fix{N,F,T} <: Function f::F x::T - function Fix{N}(f::F, x) where {N,F} + function Fix{N}(f::Union{F,Type{F}}, x) where {N,F} if !(N isa Int) throw(ArgumentError(LazyString("expected type parameter in `Fix` to be `Int`, but got `", N, "::", typeof(N), "`"))) elseif N < 1 throw(ArgumentError(LazyString("expected `N` in `Fix{N}` to be integer greater than 0, but got ", N))) end - new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) + new{N,(f isa Type{F} ? Type{F} : F),_stable_typeof(x)}(f, x) end end diff --git a/test/functional.jl b/test/functional.jl index 9961b24fe8fa9..84c4098308ebd 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -352,5 +352,12 @@ end @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `0.5::Float64`") Fix{0.5}(>, 1) @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) end + @testset "Specialize to structs not in `Base`" begin + struct MyStruct + x::Int + end + f = Fix{1}(MyStruct, 1) + @test f isa Fix{1,Type{MyStruct},Int} + end end end From c7aebb315d1f3bc4711d7c4835955158992e07f0 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 18:05:07 +0100 Subject: [PATCH 77/78] Revert "fix: change in specialization from `_stable_typeof`" This reverts commit 4999c2f24e10605fdaeb2b4560baa4aa08cb80d8. --- base/operators.jl | 4 ++-- test/functional.jl | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/base/operators.jl b/base/operators.jl index 3936925bbbb1b..bee2949e4918f 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -1170,13 +1170,13 @@ struct Fix{N,F,T} <: Function f::F x::T - function Fix{N}(f::Union{F,Type{F}}, x) where {N,F} + function Fix{N}(f::F, x) where {N,F} if !(N isa Int) throw(ArgumentError(LazyString("expected type parameter in `Fix` to be `Int`, but got `", N, "::", typeof(N), "`"))) elseif N < 1 throw(ArgumentError(LazyString("expected `N` in `Fix{N}` to be integer greater than 0, but got ", N))) end - new{N,(f isa Type{F} ? Type{F} : F),_stable_typeof(x)}(f, x) + new{N,_stable_typeof(f),_stable_typeof(x)}(f, x) end end diff --git a/test/functional.jl b/test/functional.jl index 84c4098308ebd..9961b24fe8fa9 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -352,12 +352,5 @@ end @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `0.5::Float64`") Fix{0.5}(>, 1) @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) end - @testset "Specialize to structs not in `Base`" begin - struct MyStruct - x::Int - end - f = Fix{1}(MyStruct, 1) - @test f isa Fix{1,Type{MyStruct},Int} - end end end From 8c9f62f8cb06b1fbe6460f62b7ff1d5aeaa3fd4a Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 1 Aug 2024 18:05:42 +0100 Subject: [PATCH 78/78] test: add back in test --- test/functional.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/functional.jl b/test/functional.jl index 9961b24fe8fa9..84c4098308ebd 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -352,5 +352,12 @@ end @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `0.5::Float64`") Fix{0.5}(>, 1) @test_throws ArgumentError("expected type parameter in `Fix` to be `Int`, but got `1::UInt64`") Fix{UInt64(1)}(>, 1) end + @testset "Specialize to structs not in `Base`" begin + struct MyStruct + x::Int + end + f = Fix{1}(MyStruct, 1) + @test f isa Fix{1,Type{MyStruct},Int} + end end end