Concerning parametric types only present in keyword arguments

The code excerpts are MREs.

I wrote the following method:

f(a :: T; b :: S = zero(S)) where {T, S} = convert(S, a) +  b

It is kinda obvious in hindsight it should not have worked (maybe not even allowed to exist):

> f(1)
ERROR: UndefVarError: S not defined

So I tried to adapt and failed miserably:

> g(a :: T; S = ::Type{T}, b  :: S = zero(S)) where {T, S} = convert(S, a) +  b
ERROR: syntax: function argument and static parameter names must be distinct

The problem persists if S is not a keyword parameter but a optional positional one:

> h(a :: T, S = ::Type{T}; b = zero(S)) where {T, S} = convert(S, a) +  b
ERROR: syntax: function argument and static parameter names must be distinct

And, even if S happens to be a required parameter:

> k(a :: T, S; b :: S = zero(S)) where {T, S} = convert(S, a) +  b
ERROR: syntax: function argument and static parameter names must be distinct

But if I conveniently forget to annotate the (obvious and easily inferred) type of b and remove S from the where clause then:

> k(a :: T, S; b = zero(S)) where {T} = convert(S, a) +  b
> k(1, Float64)
1.0
> k(1, Float64, b = 2)
3.0

It kinda works, if we do not consider that in an ideal world I would like the method to warn/error me because the argument of b is not of type S without needing to write any additional code (clearly, I can write an if inside the function body for that). The b parameter is silently promoted.

In my real case, I have three relevant types for the parameters of a method (with many required and keyword parameters), but the third type only appears on the keyword arguments. I wanted the method to assume the third type to be equal to the second type if no keyword/optional argument is given. There is some elegant way to do so?

Seems to me that the hearth of the issue is that Julia prefers to not mix the name of the types inferred from the parameters with the types passed as argument (in truth, with the name of parameters). I am not sure if I like the way Julia works in this very specific point. I think I would prefer if Julia assumed that the required parameter named S and the S used in type annotations of other parameters should match (be the same Type), and give me an error if they do not match, but allow me to use a type parameter to define/check the type of many other (specially optional/keyword) parameters. This kind of automatic checking is already kinda made by for “normal” cases where required parameters that share the same parametric type should all match their types.

Also, @Chris_Foster comments on another doubt of mine made me believe there was no relevant distinction between types parameters known-in-compile-time and known-in-run-time, but the error message seems to imply the opposite (by the use of the expression static parameter). Or this is just a “uh, there is a corner case where the name of a parameter and the type of another parameter are the same, lets just return a error message because the user has probably confounded two distinct things” and then the nomenclature was not the best to make the error message clear.

Ok, I am a little sleepy and did not do my homework in the best way possible, the following at works as I want, except S cannot be an optional/keyword parameter.

> k2(a :: T, ::Type{S}; b :: S = zero(S)) where {T, S} = convert(S, a) +  b
> k2(1, Float64)
1.0
> k2(1, Float64, b = 2.0)
3.0
> k2(1, Float64, b = 2)
in keyword argument b, expected Float64, got Int64

My other doubts still remain.

It is unclear what you are trying to achieve.

Keyword arguments don’t play a role in dispatch, do you want to assert their type? Then you can do

using ArgCheck: @argcheck

function f(a; S = Float64, b = zero(S))
    @argcheck b isa S
    convert(S, a) +  b
end

Also, you are not using T anywhere, so it is superfluous to parametrize on ot.

What you wrote is basically what I want, but the following is closer:

using ArgCheck: @argcheck

function f(a :: T; S = T, b = zero(S)) where {T}
    @argcheck b isa S
    convert(S, a) +  b
end

It does some things I wanted: 1) use one of the types of the required parameters for the keyword parameters, if no keyword parameter is specified, yet allow me to use a different type if I want/specify; 2) allows me to check if some keyword parameters share the same type (yet having to write extra code instead of just giving the keyword parameters type annotations).

However, this fails at the following:

> f(1; b = 2.0)
ERROR: ArgumentError: b isa S must hold. Got
b => 2.0
S => Int64

I wanted to be able to compute a default value based on the assumption that S == T if no keyword parameter is given, but at the same time, if a keyword parameter is given, I want the method to use the keyword parameter type, not to try to enforce the S == T assumption (that is just a default).

I suppose that I can solve my problem making my code considerably ugly:

using ArgCheck: @argcheck

function f(a :: T; b = nothing, c = nothing) where {T}
    if isnothing(b)
        if isnothing(c)
            b = one(T)
            c = zero(T)
        else
            b = one(c)
        end
    else
        if isnothing(c)
            c = zero(b)
        else
            @argcheck typeof(b) == typeof(c)
        end
    end
    convert(typeof(b), a) +  b + c
end

Also, you are not using T anywhere, so it is superfluous to parametrize on ot.

In my real case, the two first types are shared among more than one required parameter so T serve to check if some parameters share the same type. Not only that, but as I said above, I would want to use of one of the two types present in required parameters as default to some keyword arguments type if they are not specified.

I still don’t quite get what you want to do, but could it be

f(a; b = zero(a)) =  a + b

possibly called as

f(convert(S, a))

if you want to convert?

Generally, I think it is best to

  1. use small, composable pieces,

  2. use type information only when necessary for dispatch,

  3. rely on Julia’s promotion mechanisms.

Nevermind, it is clear that I am not able to make myself to be understood.

I said in the first line of the first post that the examples were MREs, and explained what behavior I wanted, and finally found an MRE that shows the behavior I wanted while in a very ugly fashion. Seems like the language has no intrinsic mechanism to do what I want and I will need to rely on codifying this behavior by hand.

Your alternative does not work for my real case, nor any of your three suggestions. Please, forget about this.