|
1 | 1 | module EnforcedTypeSignatureCallables |
2 | 2 |
|
3 | | -export TypedCallable |
| 3 | +export CallableWithReturnType, CallableWithTypeSignature, typed_callable |
| 4 | + |
| 5 | +struct CallableWithArgumentTypes{Arguments <: Tuple, Callable} <: Function |
| 6 | + callable::Callable |
| 7 | + function CallableWithArgumentTypes{Arguments}(callable::Callable) where {Arguments <: Tuple, Callable} |
| 8 | + callable_type = if callable isa Type |
| 9 | + Type{callable} |
| 10 | + else |
| 11 | + typeof(callable) |
| 12 | + end |
| 13 | + new{Arguments, callable_type}(callable) |
| 14 | + end |
| 15 | +end |
| 16 | + |
| 17 | +function Base.propertynames((@nospecialize unused::CallableWithArgumentTypes), ::Bool = false) |
| 18 | + () |
| 19 | +end |
| 20 | + |
| 21 | +function (callable::CallableWithArgumentTypes)(args...; kwargs...) |
| 22 | + function arguments(::CallableWithArgumentTypes{Arguments}) where {Arguments <: Tuple} |
| 23 | + Arguments |
| 24 | + end |
| 25 | + args = args::arguments(callable) |
| 26 | + c = getfield(callable, 1) |
| 27 | + c(args...; kwargs...) |
| 28 | +end |
4 | 29 |
|
5 | 30 | """ |
6 | | - TypedCallable |
| 31 | + CallableWithReturnType <: ComposedFunction |
| 32 | +
|
| 33 | +Type of callables with a guaranteed return type. |
| 34 | +
|
| 35 | +Has two type variables: |
| 36 | +
|
| 37 | +* the return type |
7 | 38 |
|
8 | | -A simple callable type wrapping another callable. |
| 39 | +* the underlying callable type |
9 | 40 |
|
10 | | -There are three type parameters: the first type parameter represents the allowed types |
11 | | -of the positional arguments. The second type parameter is the allowed return type of the |
12 | | -callable. The third type parameter is the type of the wrapped callable. |
| 41 | +`CallableWithReturnType` is not a newly defined type. It is merely a type alias based on: |
13 | 42 |
|
14 | | -The first type parameter, representing the allowed positional argument types, always |
15 | | -subtypes `Tuple`. For example, to allow either a single `Int` argument or two `Bool` |
16 | | -arguments, choose `Union{Tuple{Int},Tuple{Bool,Bool}}` as the first type parameter. |
| 43 | +* `ComposedFunction` |
17 | 44 |
|
18 | | -To disable argument type checking, just choose `Tuple` as the first parameter. |
| 45 | +* `Base.Fix2` |
19 | 46 |
|
20 | | -To disable return type checking, just choose `Any`, as the second parameter. |
| 47 | +* `typeassert` |
| 48 | +
|
| 49 | +For some type `Return` we have the following identity which allows dispatching on a function with a certain guaranteed return type: |
| 50 | +
|
| 51 | +```julia |
| 52 | +CallableWithReturnType{Return} == ComposedFunction{Base.Fix2{typeof(typeassert), Type{Return}}} |
| 53 | +``` |
| 54 | +
|
| 55 | +Lacks constructor methods. Construct a `CallableWithReturnType` using [`typed_callable`](@ref). |
21 | 56 | """ |
22 | | -struct TypedCallable{A<:Tuple,R,F} |
23 | | - f::F |
| 57 | +const CallableWithReturnType = ComposedFunction{ |
| 58 | + Base.Fix2{ |
| 59 | + typeof(typeassert), |
| 60 | + Type{Return}, |
| 61 | + }, |
| 62 | + Callable, |
| 63 | +} where { |
| 64 | + Return, |
| 65 | + Callable, |
| 66 | +} |
24 | 67 |
|
25 | | - """ |
26 | | - TypedCallable{A,R}(f) |
| 68 | +""" |
| 69 | + CallableWithTypeSignature <: CallableWithReturnType |
27 | 70 |
|
28 | | - Construct a `TypedCallable{A,R}` wrapping the callable `f`. |
29 | | - """ |
30 | | - function TypedCallable{A,R}(f::F) where {A<:Tuple,R,F} |
31 | | - t_r = R::Type |
32 | | - new{A,t_r,F}(f) |
33 | | - end |
| 71 | +Type of callables with a guaranteed return type and argument types. |
| 72 | +
|
| 73 | +Has three type variables: |
| 74 | +
|
| 75 | +* the return type |
| 76 | +
|
| 77 | +* the type of the positional (non-keyword) arguments, subtypes `Tuple` |
| 78 | +
|
| 79 | +* the underlying callable type |
| 80 | +
|
| 81 | +Lacks constructor methods. Construct a `CallableWithTypeSignature` using [`typed_callable`](@ref). |
| 82 | +""" |
| 83 | +const CallableWithTypeSignature = CallableWithReturnType{ |
| 84 | + Return, |
| 85 | + CallableWithArgumentTypes{ |
| 86 | + Arguments, |
| 87 | + Callable, |
| 88 | + }, |
| 89 | +} where { |
| 90 | + Return, |
| 91 | + Arguments <: Tuple, |
| 92 | + Callable, |
| 93 | +} |
| 94 | + |
| 95 | +function return_type_enforcer(::Type{Return}) where {Return} |
| 96 | + Base.Fix2(typeassert, Return) |
34 | 97 | end |
35 | 98 |
|
36 | 99 | """ |
37 | | - (tc::TypedCallable{A,R})(args...; kwargs...) |
| 100 | + typed_callable(return_type::Type, argument_types::Type{<:Tuple}, callable)::CallableWithTypeSignature{return_type, argument_types} |
| 101 | +
|
| 102 | +Creates a callable from `callable` with: |
| 103 | +
|
| 104 | +* guaranteed return type `return_type` |
| 105 | +
|
| 106 | +* guaranteed argument types `argument_types` |
| 107 | +
|
| 108 | +The return type is [`CallableWithTypeSignature`](@ref). |
| 109 | +
|
| 110 | +Examples: |
| 111 | +
|
| 112 | +```julia-repl |
| 113 | +julia> using EnforcedTypeSignatureCallables |
| 114 | +
|
| 115 | +julia> typed_callable(Float32, Tuple{Float32, Float32}, hypot)(3.1f0, 3.0f0) |
| 116 | +4.313931f0 |
| 117 | +
|
| 118 | +julia> typed_callable(Float32, Tuple{Float32, Float32}, hypot)(3.1f0, 3.0) |
| 119 | +ERROR: TypeError: in typeassert, expected Tuple{Float32, Float32}, got a value of type Tuple{Float32, Float64} |
| 120 | +``` |
| 121 | +""" |
| 122 | +function typed_callable(::Type{Return}, ::Type{Arguments}, callable::Callable) where { |
| 123 | + Return, Arguments <: Tuple, Callable, |
| 124 | +} |
| 125 | + ret = return_type_enforcer(Return) |
| 126 | + with_argument_types = CallableWithArgumentTypes{Arguments}(callable) |
| 127 | + ret ∘ with_argument_types |
| 128 | +end |
| 129 | + |
| 130 | +""" |
| 131 | + typed_callable(return_type::Type, callable)::CallableWithReturnType{return_type} |
| 132 | +
|
| 133 | +Creates a callable from `callable` with guaranteed return type `return_type` |
| 134 | +
|
| 135 | +The return type is [`CallableWithReturnType`](@ref). |
| 136 | +
|
| 137 | +Examples: |
| 138 | +
|
| 139 | +```julia-repl |
| 140 | +julia> using EnforcedTypeSignatureCallables |
| 141 | +
|
| 142 | +julia> typed_callable(Int, Int)(3) |
| 143 | +3 |
| 144 | +
|
| 145 | +julia> typed_callable(Int, Int) isa CallableWithReturnType{Int} |
| 146 | +true |
| 147 | +
|
| 148 | +julia> typed_callable(Float64, cos)(3) |
| 149 | +-0.9899924966004454 |
38 | 150 |
|
39 | | -1. Enforces `args isa A` |
40 | | -2. Calls `tc.f` with the provided positional and keyword arguments |
41 | | -3. Enforces the return type of the above call as `R` |
| 151 | +julia> typed_callable(Float32, cos)(3.0) |
| 152 | +ERROR: TypeError: in typeassert, expected Float32, got a value of type Float64 |
| 153 | +``` |
42 | 154 | """ |
43 | | -function (tc::TypedCallable{A,R})(args::Vararg{Any,N}; kwargs...) where {A,R,N} |
44 | | - args = args::A |
45 | | - r = (tc.f)(args...; kwargs...) |
46 | | - r::R |
| 155 | +function typed_callable(::Type{Return}, callable::Callable) where { |
| 156 | + Return, Callable, |
| 157 | +} |
| 158 | + ret = return_type_enforcer(Return) |
| 159 | + ret ∘ callable |
47 | 160 | end |
48 | 161 |
|
49 | 162 | end |
0 commit comments