Simple parametric type question


#1

Hi folks,
I guess I have trouble defining a struct of a giving type and creating it, just because of the (failing to know/understand) the syntax. I was used to create structs in the form

mutable struct crocodile
   teeth :: Int
   legs  :: Float64
   crocodile() = new()
end

such that I can just create an object of this kind by simply hitting

wally_gator() = new()

and fill in only the fields I want to have, leaving the rest undefined

wally_gator.legs = 4

Now the thing is that I want to do the same, but using a parametrized type, something of the form

mutable struct animal{T}
   x :: Int
   y :: T
   ...
end

I wonder what the equivalent to the

crocodile() = new()

statement is, including the T type argument. I’ve tried things like

animal{T}) where T = new{T}()

but that fails when trying to create an instance of this object with

krok = animal{Float64}()

Could you please provide some help?

Thx,

Ferran.


#2

Not sure what the actual question is, but syntactically you’re missing an end:

mutable struct crocodile end

#3

There are two sorts of struct one is spelled mutable struct and the other is spelled struct. A struct has fields that are value associated at the time that a realization/occurance/instance of the struct is constructed. A mutable struct has fields that are initially associated at that same event-time; the value immediately assigned to a field (the name of the thing associated) of a mutable struct can be updated/altered. If you have a struct with a field that holds a container of some sort, you can alter the contents of the container – you cannot alter the specific container given that field. mutable structs have fields you can mutate/alter.


#4

What is the T? What type[s] can the field y be?

Often the parameter is used to differentiate e.g. the type of number (Float/Int and/or sizeof(Float/Int)) or to track a count (like the number of entries in a Tuple: (1, 2, 3) has 3 entries and is of type NTuple{3,Int}. If you have a field that may take 2-tuples or 3-tuples, for example, then you may use a parameter to capture the specific length of tuple that occurs in each instance:

struct KeepLengthInParam{N}
    value::NTuple{N, Int}  # the N here is the same N as the N parameter
   
    function KeepLengthInParam(value::NTuple{N, Int}) where {N}
        return new{N}(value)
    end
end

example1 = KeepLengthInParam( (1, 2, 3) )
typeof(example1)

example2 = KeepLengthInParam( (1, 2) )
typeof(example2)

try it


#5

either Float64 or Int, essentially


#6
struct FloatOrInt{T}
    value::T
   
    function FloatOrInt(value::T) where {T<:Union{Float64, Float32, Int64, Int32}}
        return new{T}(value)
    end
end

that should do it

if you want to get at the parameter directly

whatisit(x::FloatOrInt{T}) where {T} = T

a = FloatOrInt(5)
b = FloatOrInt(5.0)

whatisit(a)
whatisit(b)

#7

On a general note, it’s helpful if you indicate updates of your original post.


#8

sorry I don’t know how to use your solution or this does not seem to work. When I try

u = FloatOrInt{Float64}()

I get errors…

MethodError: no method matching FloatOrInt()
Closest candidates are:
  FloatOrInt(!Matched::T<:Union{Float32, Float64, Int32, Int64}) where T<:Union{Float32, Float64, Int32, Int64} at In[8]:5

Stacktrace:
 [1] top-level scope at In[12]:100:

#9
u = FloatOrInt(0)

if you want to avoid writing the zero (that’s a bad idea)

struct FloatOrInt{T}
    value::T
   
    function FloatOrInt(value::T) where {T<:Union{Float64, Float32, Int64, Int32}}
        return new{T}(value)
    end
end

FloatOrInt() = FloatOrInt(0)

u = FloatOrInt()

#10

Ok that works because your struct has only one field. The fact is that i do want to initialize as FloatOrInt() with NO arguments, just because I this is a reduction from a quite big type with many fields. I hate to have to give default values to everything, mostly considering that in some simulations I use some fields and leave the rest undefined, while in other I use other fields etc. Declaring like

u = crocodile()

and then only filling the fields I need

u.teeth = 128

is what I want/need. Can this be done? Please notice that replies of the form ‘this is the wrong way to do things, you have to build different types…’ is not useful as by doing that I’d have to change tons of lines of code…

Thanks again,

Ferran.


#11

Please see the code just above your question – it shows you what to do.

MyStruct() = MyStruct( <put the default values here once> )
# now , any time you do
x = MyStruct()
# the default values attach

#12

…but there are no default values, that’s what I wanted to say. Is it mandatory to have them? On the original type I posted you see there are no default values.


#13

I do not understand what you mean. It is possible to declare fields to have either an Int or a Float or be missing. Is that what you want? What do you do with a struct of fields that have nothing assigned?

A struct has fields that are value associated at the time each instance of the struct is constructed.


#14

Yes I need to have fields with nothing assigned… As I said, this is because I use large types where only some fields are filled in one simulation, while other fields are filled in other simulations. Please read the original post, the crocodile() type I posted has two fields, I declare wally_gator as a new object of this type, assign one field and do nothing with the other, it remains unassigned.
I want to reproduce this behaviour with the parametrized type, if possible…


#15

You can try this:

julia> mutable struct animal{T}
          x :: Int
          y :: T
          animal{T}() where {T} = new()
       end

julia> krok = animal{Float64}()
animal{Float64}(112604960, 8.10255564e-316)

julia> krok.x=2
2

julia> krok.y=50.0
50.0

julia> krok
animal{Float64}(2, 50.0)

#16

I did read your original post. Perhaps you might revisit mine. What you want is way to denote that some fields are absent, devoid of a set value and in other runs those fields may get assigned a specific value and others may be “unused” — even when a field is “unused” it exists and as it exists, you do not want it storing garbage; you want to be able to assign the value that means “this is an unused field” which is a value. There is a way to do this, although it is unclear that that would work best for you.

When a field holds the value “this field is not being used now”, what happens in the evaluation routine … is that field ever accessed to see if it is being used or not? Or do you have multiple routines, each of which accesses only a specific subset of the fields available? The approach that is most appropriate depends on these sorts of considerations. There is no immediate match to the syntax that you provided originally, that is not Julia code. There are approaches which likely get done what you need done. I understand that you feel you have entirely specific … please realize there may be considerations and qualifications that you had not yet made clear.


#17

Oh… sorry I I sounded a bit too rough but that was not my intention. I’m not an English native speaker and sometimes the fine details of the proper narrative escape me. In fact I can only thank you for your help and efforts, any other side effect was totally unwanted.

I will try your last form once I’m back at the office. I tried many different combinations similar to this with different {} and () combinations, but maybe not this one (which will probably be the right one). I’ll report back once I try…

Your considerations about different possibilities and interpretations may be right. I certainly had the impression I was completely specifying the problem, but maybe I was not as you say…

Thanks again,

Ferran


#18
julia> mutable struct animal{T}
          x :: Int
          y :: T
          animal{T}() where{T} = new{T}()
       end

julia> a = animal{Float64}()
animal{Float64}(4715867728, 5.0e-324)

julia> a.x = 2
2

#19

I have seen this approach in many different guises.
As a matter of design practice and out of compassion for my clients, the only situation in which I establish an unknown, nonrepeatable initial assigned state is where I supply all the overwriting immediately thereafter (as with preallocating a vector in Julia).

Where all the struct fields were to be valued following construction, perhaps having been queued, this makes sense to me.


#20

Im just answering the question. :man_shrugging:

But yeah, leaving isbitstype fields uninitialized can easily lead to bugs.