Getting UndefVarError: new not defined when trying to define a struct with an inner constructor in Julia

Viewed 537

I'm new to Julia and trying out some code I copied from this article. It's supposed to be a way to do object-oriented programming (i.e. classes) in Julia:

using Lathe.stats: mean, std

struct NormalDistribution{P}
    mu::Float64
    sigma::Float64
    pdf::P
end

function NormalDistribution(x::Array)
        pdf(xt::Array) = [i = (i-μ) / σ for i in xt]
        return new{typeof(pdf)}(mean(x), std(x), pdf)
end

x = [5, 10, 15, 20]
dist = NormalDistribution(x)

However, when I run this with Julia 1.1.1 in a Jupiter notebook I get this exception:

UndefVarError: new not defined

Stacktrace:
 [1] NormalDistribution(::Array{Int64,1}) at ./In[1]:11
 [2] top-level scope at In[1]:15

I found this documentation page on inner constructor methods which explains that they have

a special locally existent function called new that creates objects of the block's type.

(although the documentation for new linked above says it is a keyword).

It's possible I may have copied the code incorrectly but maybe someone could explain how to implement what the original author was proposing in the article. Also, I don't know how to debug in Julia yet so any pointers appreciated.

3 Answers

What the documentation page you linked to means to say is that the new keyword only exists within inner constructors (as opposed to outer constructors).

So either you go for an outer constructor, in which case you want to use the default constructor in order to actually create your new instance:

using Statistics

# First the type declaration, which comes with a default constructor
struct NormalDistribution{P}
    mu::Float64
    sigma::Float64
    pdf::P
end

# Another outer constructor
function NormalDistribution(x::Array)
    μ = mean(x)
    σ = std(x)
    pdf(xt::Array) = [(i-μ) / σ for i in xt]

    # This is a call to the constructor that was created for you by default
    return NormalDistribution(μ, σ, pdf)
end
julia> x = [5, 10, 15, 20]
4-element Array{Int64,1}:
  5
 10
 15
 20

julia> dist = NormalDistribution(x)
NormalDistribution{var"#pdf#2"{Float64,Float64}}(12.5, 6.454972243679028, var"#pdf#2"{Float64,Float64}(12.5, 6.454972243679028))

julia> dist.pdf([1, 2, 3])
3-element Array{Float64,1}:
 -1.781572339255412
 -1.626653005407115
 -1.4717336715588185

Note that the definition of pdf in the original article is obviously problematic (if only because μ and σ are not defined). I tried to modify it so that it makes some sense

One possible issue with this is that anyone can define a NormalDistribution instance with an inconsistent state:

julia> NormalDistribution(0., 1., x->x+1)
NormalDistribution{var"#11#12"}(0.0, 1.0, var"#11#12"())

Which is why you might want to actually define an inner constructor, in which case Julia does not provide you with a default constructor but you get access to that special new function which creates objects of the type you're defining:

# A type declaration with an inner constructor
struct NormalDistribution2{P}
    mu::Float64
    sigma::Float64
    pdf::P

    # The inner constructor is defined inside the type declaration block
    function NormalDistribution2(x::Array)
        μ = mean(x)
        σ = std(x)
        pdf(xt::Array) = [(i-μ) / σ for i in xt]

        # Use the `new` function to actually create the object
        return new{typeof(pdf)}(μ, σ, pdf)
    end
end

It behaves exactly in the same way as the struct with an outer constructor, except this time no default constructor is provided anymore:

julia> dist2 = NormalDistribution2(x)
NormalDistribution2{var"#pdf#5"{Float64,Float64}}(12.5, 6.454972243679028, var"#pdf#5"{Float64,Float64}(12.5, 6.454972243679028))

julia> dist2.pdf([1, 2, 3])
3-element Array{Float64,1}:
 -1.781572339255412
 -1.626653005407115
 -1.4717336715588185

# No default constructor provided
julia> NormalDistribution2(0., 1., x->x+1)
ERROR: MethodError: no method matching NormalDistribution2(::Float64, ::Float64, ::var"#9#10")
Stacktrace:
 [1] top-level scope at REPL[13]:1

An inner constructor has to be defined inside of the struct definition body; this is the only place where new has any special meaning. Your constructor method is outside of the struct definition where new is just a regular (undefined) name.

Ok. Looks like you were very confused. So that function is meant to be included as an inner constructor to the outer constructor before the end. The problem is the code is divided and built upon slowly across the article where only parts of the whole constructor are shown. If you are interested, and like my blog, I am about to post a video on outer constructors and inner constructors on my channel, https://youtube.com/playlist?list=PLCXbkShHt01seTlnlVg6O7f6jKGTguFi7 , it will be part 9 ( I am editing it now,) maybe watching this in video form will make it make more sense. enter image description here

Related