Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

julia parametric constructor - problems with outer constructor

I am trying to implement a GF type following Andreas Noack's sketch in his 2015 Stanford lecture, but am having some problems early on. I am using Julia 0.3.10

His relevant 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 appears when you try and just define something like this

GF{2}(11)

You get

type cannot be constructed

Okay, so there is not an automatic constructor.

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

The problem is with no automatic constructor other functions (like zero(x)) fail.

Attempts at making an external constructor haven't been working for me:

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

Warning: static parameter P does not occur in signature for GF at In[4]:1. The method will not be callable.

Basically I am running out of ideas as to how to specify that a call like GF{3}(x) should create an instance of GF{3,typeof(x)}(x)

I know I am missing something blindingly obvious.

Thanks

like image 938
Robin Avatar asked Jul 10 '15 07:07

Robin


1 Answers

Unfortunately, this is simply not possible on 0.3. The ability to overload call is one of the big new features that will be available in 0.4. This feature is required to call a incompletely-parameterized type like GF{2}. It looks like Andreas was using a 0.4-dev version for this demo; if you'd like to follow his demo directly, I'd recommend doing the same.


More details and a work-around:

In 0.3, you call a type's inner constructor by simply calling the type — but it must be a concrete (or [leaf]) type. That means that it must be fully parameterized if it has type parameters. In this case, it means that you will need to manually specify the integer type in order to call the inner constructor: GF{2,Int}(5).

You can also define outer constructors, which look and behave just like a generic function that happens to have the same base name. You can add type parameters to generic functions, too, but while they look similar to the parameters of a type (especially when the names are the same), they behave very differently! The parameters of a function are defining a local variable that will be used to match against the types of the arguments. That's why your definition GF{P}(x::Integer) = GF{P,Int64}(x) is throwing that warning: since you never use P to define the argument types, Julia won't be able to figure out what P should be, and so it will never be callable. We could create a function that always makes a 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)

Note that I didn't specify what T should be when I called GF2 — Julia figured that out. This just gets confusing when you're defining an outer constructor for a parameterized type, since GF{P}(x::Integer) = … is this overlapping point where the function parameter and type parameter look the same! The function parameter wins, and so while you can define an outer constructor with parameters, those parameters mean something different and you call the outer constructor without them: GF(…). You can call the inner constructor, but there you have to specify all the parameters: GF{2, Int}(…). There's no middle ground in 0.3.

This changes in 0.4: Now you can define what happens when you call an arbitrary object! That's what function call{P}(::Type{GF{P}}, x::Integer) is defining: if you call the incomplete type GF{2}, then this method will be called with P=2. In fact, this generalizes outer constructors. An outer constructor (even if it's parameterized) is simply a "sugar" for defining call(::Type{GF}, x::Integer) for the type GF without any parameters. So this is how 0.4 enables all sorts of great behaviors with call overloading.


If you really want to make this work on 0.3, you could either define functions like GF2 above that hard-code the P value, or you could 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 inner constructor is exactly what you wanted: GF{P}, so you can call GF{2}(5) directly. But you've lost flexibility in the integer type that is being used. There are other tricks, too, but that's for another time.

like image 151
mbauman Avatar answered Oct 15 '22 21:10

mbauman