From 2a411c0d4ad27431ae081846d63f3958b805deee Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Wed, 17 Jun 2020 16:31:27 +0200 Subject: [PATCH 1/2] make bind work with keyword functions --- src/Curry.jl | 12 +++++++----- test/runtests.jl | 25 +++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Curry.jl b/src/Curry.jl index a03158e..c85a676 100644 --- a/src/Curry.jl +++ b/src/Curry.jl @@ -62,19 +62,20 @@ b(", world") `Bind(f, (g(), h()))` is like `:(f(g(), h()))` but `f` `g` and `h` are lexically scoped, and `g()` and `h()` are evaluated eagerly. """ -struct Bind{F, A} <: Function +struct Bind{F, A, K} <: Function f::F a::A + k::K end -function (c::Bind)(args...) - c.f(interleave(c.a, args)...) +function (c::Bind)(args...; kw...) + c.f(interleave(c.a, args)...; c.k..., kw...) end """ `bind(f, a, b)` is equivalent to Bind(f, (a, b)) """ -bind(f, args...) = Bind(f, args) +bind(f, args...; kw...) = Bind(f, args, kw) """ `@bind f(a,b)` macroexpands to `bind(f, a, b)` @@ -83,7 +84,8 @@ bind(f, args...) = Bind(f, args) macro bind(ex) ex.head == :call || error() # `ex.args[1]` is the function and `ex.args[2:end]` are the positional arguments - return :(bind($(map(esc, ex.args)...))) + TODO_HANDLE_KW = () + return :($bind($(map(esc, ex.args)...))) end end diff --git a/test/runtests.jl b/test/runtests.jl index 22d6666..62d0d2e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,27 @@ using Curry +using Curry: bind using Test +@testset "keywords" begin + @test bind(≈, 1, nothing, atol=1.1)(2) + @test bind(≈, 1, nothing, )(2, atol=1.1) + @test !bind(≈, 1, nothing, atol=1.1)(2, atol=0.9) + @test !bind(≈, 1, nothing, atol=0.9)(2) + @test bind(≈, 1, nothing, atol=0.9)(2, atol=1.1) + @test bind(≈, 1, nothing, atol=0.9)(2, atol=2) + + @test bind(≈, nothing, nothing, atol=1.1)(1,2) + @test !bind(≈, nothing, nothing, atol=0.9)(1,2) + a2 = @inferred bind(≈, nothing, nothing) + b2 = @inferred bind(≈, nothing, nothing, atol=1) + c2 = @inferred bind(≈, nothing, nothing, atol=1, rtol=2) + a1 = @inferred bind(≈, 3, nothing, atol=1, rtol=2) + @inferred a2(1,2) + @inferred b2(1,2) + @inferred c2(1,2) + @inferred a1(1.0) +end + @testset "Bind.jl" begin #= @@ -40,9 +61,9 @@ using Test @test eager == lazy - is3 = Bind(==, (3, nothing)) + is3 = bind(==, 3, nothing) @test false == @inferred is3(4) - isnothing2 = Bind(===, (Some(nothing), nothing)) + isnothing2 = Bind(===, (Some(nothing), nothing), ()) @test isnothing2(nothing) @test isnothing2(:notnothing) == false From 3736988639b13d4e4763866b5f886034a4c0e00f Mon Sep 17 00:00:00 2001 From: Jan Weidner Date: Thu, 18 Jun 2020 07:42:28 +0200 Subject: [PATCH 2/2] improve docs --- src/Curry.jl | 56 ++++++++++++++++++++++++++++------------------- test/Project.toml | 1 + test/runtests.jl | 4 ++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/Curry.jl b/src/Curry.jl index c85a676..b29fc65 100644 --- a/src/Curry.jl +++ b/src/Curry.jl @@ -7,6 +7,7 @@ export Bind, @bind Return a `Tuple` that interleaves `args` into the `nothing` slots of `slots`. ```jldoctest +using Curry Curry.interleave((:a, nothing, :c, nothing), (12, 34)) # output @@ -17,6 +18,7 @@ Curry.interleave((:a, nothing, :c, nothing), (12, 34)) Use `Some` to escape `nothing` ```jldoctest +using Curry Curry.interleave((:a, Some(nothing), :c, nothing), (34,)) # output @@ -41,26 +43,6 @@ _interleave(firstbind::T, tailbind::Tuple, args::Tuple) where T = ( """ Represent a function call, with partially bound arguments. - -```jldoctest -b = Bind(+, (1, 2)) -b() - -# output - -3 -``` - -```jldoctest -b = Bind(*, ("hello", nothing)) -b(", world") - -# output - -"hello, world" -``` - -`Bind(f, (g(), h()))` is like `:(f(g(), h()))` but `f` `g` and `h` are lexically scoped, and `g()` and `h()` are evaluated eagerly. """ struct Bind{F, A, K} <: Function f::F @@ -73,7 +55,38 @@ function (c::Bind)(args...; kw...) end """ -`bind(f, a, b)` is equivalent to Bind(f, (a, b)) + `bind(f, a, b)` + `bind(f, args...; kw...)` + +The `bind` function partially evaluates `f` by binding some of its arguments. +Positional arguments of `f` that should not be bound are indicated by passing `nothing` +to `bind` at the respective position. +```jldoctest +julia> using Curry: bind + +julia> b = bind(+, 1, 2); # no nothing, all arguments bound + +julia> b() +3 + +julia> b = bind(*, "hello", nothing); # only first argument bound + +julia> b(", world") +"hello, world" + +julia> b = bind(=>, nothing, 1); # second argument bound + +julia> b("one") +"one" => 1 + +julia> b = bind(isapprox, nothing, nothing, atol=100); # only atol keyword bound + +julia> b(10, 20) +true + +julia> b(10, 20, atol=1) # keywords can be reassigned on the fly +false +``` """ bind(f, args...; kw...) = Bind(f, args, kw) @@ -84,7 +97,6 @@ bind(f, args...; kw...) = Bind(f, args, kw) macro bind(ex) ex.head == :call || error() # `ex.args[1]` is the function and `ex.args[2:end]` are the positional arguments - TODO_HANDLE_KW = () return :($bind($(map(esc, ex.args)...))) end diff --git a/test/Project.toml b/test/Project.toml index 0c36332..e7acf34 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,2 +1,3 @@ [deps] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" diff --git a/test/runtests.jl b/test/runtests.jl index 62d0d2e..8904a86 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using Curry using Curry: bind using Test + @testset "keywords" begin @test bind(≈, 1, nothing, atol=1.1)(2) @test bind(≈, 1, nothing, )(2, atol=1.1) @@ -71,3 +72,6 @@ end b = @bind "hey" * nothing * "there" @test b(", you, ") == "hey, you, there" end + +using Documenter: doctest +doctest(Curry)