Julia parametric constructor - problems with an external constructor

I am trying to implement the GF type after a sketch by Andreas Noack in my lecture at Stanford in 2015, but I have some problems at an early stage. I am using Julia 0.3.10

Its corresponding code is as follows:

# Scalar finite fields immutable GF{P,T<:Integer} <: Number data::T function GF(x::Integer) return new(mod(x, P)) end end # methods for scalar finite field import Base: convert, inv, one, promote_rule, show, zero function call{P}(::Type{GF{P}}, x::Integer) if !isprime(P) throw(ArgumentError("P must be a prime")) end return GF{P,typeof(x)}(mod(x, P)) end convert{P,T}(::Type{GF{P,T}}, x::Integer) = GF{P}(x) convert{P}(::Type{GF{P}}, x::Integer) = GF{P}(x) convert{P,T}(::Type{GF{P,T}}, x::GF{P}) = GF{P,T}(x.data) promote_rule{P,T1,T2<:Integer}(::Type{GF{P,T1}}, ::Type{T2}) = GF{P,promote_type(T1,T2 )} show(io::IO, x::GF) = show(io, x.data) 

So, the problem arises when you try to just define something like this

 GF{2}(11) 

You are getting

can't build

Good, therefore there is no automatic constructor.

GF{2,Int64}(11) working fine.

The problem is the lack of an automatic constructor. Other functions (e.g. zero (x)) fail.

Attempts to make an external constructor do not work for me:

I think GF{P}(x::Integer) = GF{P,Int64}(x) should work, but I get

Warning: the static parameter P does not occur in the signature for GF with In [4]: โ€‹โ€‹1. This method will not be called.

I basically end up with ideas on how to indicate that a call to GF {3} (x) should create an instance of GF {3, typeof (x)} (x)

I know that I'm missing something dazzlingly obvious.

thanks

+5
source share
1 answer

Unfortunately, this is simply not possible at 0.3. call overloading is one of the great new features that will be available in version 0.4. This function is required to call an incompletely parameterized type of type GF{2} . Andreas seems to be using the 0.4-dev version for this demo; if you want to directly follow his demo, I would recommend doing the same.


Details and workaround:

In 0.3, you call the internal constructor of the type, just calling the type, but it must be specific (or [leaf]). This means that it must be fully parameterized if it has type parameters. In this case, this means that you will need to manually specify the integer type to call the internal constructor: GF{2,Int}(5) .

You can also define external constructors that look and behave just like a generic function that has the same base name. You can add type parameters to generic functions, but although they look similar to type parameters (especially when the names are the same), they behave differently! Function parameters define a local variable that will be used to match argument types. That's why your definition of GF{P}(x::Integer) = GF{P,Int64}(x) throws this warning: since you never use P to determine argument types, Julia will not be able to determine what P should be, and therefore, it will never be called. We could create a function that always does GF{2} :

 julia> GF2{T}(x::T) = GF{2,T}(x) # Call the fully parameterized inner constructor GF2 (generic function with 1 method) julia> GF2(3) GF{2,Int64}(1) 

Please note that I did not indicate what T should be when I called GF2 - Julia understood this. This is just confusing when you define an external constructor for a parameterized type, since GF{P}(x::Integer) = โ€ฆ is an overlapping point where the function parameter and type parameter look the same! The function option wins, and therefore, while you can define an external constructor with parameters, these parameters mean something else, and you call the external constructor without them: GF(โ€ฆ) . You can call the internal constructor, but there you must specify all the parameters: GF{2, Int}(โ€ฆ) . There is no middle place at 0.3.

This changes to 0.4: now you can determine what happens when you call an arbitrary object! That function call{P}(::Type{GF{P}}, x::Integer) defines: if you call the incomplete type GF{2} , then this method will be called using P=2 . In fact, this generalizes external constructors. The external constructor (even if it is parameterized) is simply a โ€œsugarโ€ for defining call(::Type{GF}, x::Integer) for type GF without any parameters. So this is how 0.4 provides all kinds of excellent call overload behavior.


If you really want to make this work 0.3, you can either define functions like GF2 above that hard code, the value of P , or you can make the type less flexible:

 immutable GF{P} <: Number data::Int function GF(x::Integer) return new(mod(convert(Int, x), P)) end end 

Now the internal constructor is exactly what you wanted: GF{P} , so you can directly call GF{2}(5) . But you have lost flexibility in the integer type used. There are other tricks, but at a different time.

+5
source

All Articles