How to detect if an object is a user-defined struct


#1

Hi,
I’m building a function that receives an object as input, and depending on whether the type of this object is standard or user-defined (=composite type defined by the user through a struct) it must do one thing or another. Something like

function kroky(x)
   if typeof(x) == user_defined
      println("user-defined variable")
   else
      println("system-defined variable")
   end
end

such that if I define

mutable struct conch
  x :: Int
  y :: Float64
  conch() = new()
end

then with

zz = conch()
zz.x = 1
zz.y = 3.1416

one has

kroky(zz)
> user-defined variable
kroky(3.0)
> system-defined variable

Of course the action example is quite basic in this case, but I hope you get the idea of what I am after.

Thx for your help and happy new year :slight_smile:

Ferran


#2

This will be difficult since Julia is designed so that user-defined constructs are first class citizens. So in principle you have no reason to care.

That said, if you need this for eg introspection or debugging, you can use parentmodule(T) and check if it is Base (but you may need to walk up the chain of modules) or a standard library.


#3

May I ask what the use case is for this? It sounds a bit like code smell; perhaps there are other ways to achieve what you’re trying to do?


#4

As Tamas says, the concept of a user-defined type doesn’t really exist in Julia. However, there do exist primitive types (e.g., integers, floats, other chunks of raw memory), though primitive types might well not include all the types you are interested in. (And users are free to define their own new primitive types!)

julia> isprimitivetype(typeof(1.0))  # Float64
true

julia> isprimitivetype(typeof(1))  # Int64
true

julia> isprimitivetype(typeof((1.0,2.0)))  # Tuple{Float64, Float64}
false

julia> isprimitivetype(typeof("a"))  # String
false

julia> isprimitivetype(typeof('a'))  # Char
true

#5

It might help to get at which meaning of this you want by asking: what should happen on the future if Julia introduces a new predefined type? Would your code break if that was included or if it wasn’t?


#6

Maybe I can put things in a different way. I have user-defined objects that have other user-defined objects as some of its fields, ex

mutable struct krok
   x :: Int64
   y :: Float64
   krok() = new()
end

mutable struct reptil
   a :: Int64
   b :: krok
   reptil() = new()
end

such that I declare

z = krok()
z.x = 1
z.y = 3.1416

R = reptil()
R.a = 0
R.b = z

Then the idea is that the function, when passed R as an argument, must print R.a and call itself over R:b to print its contents, stored in z.

Somehow I have the impression I can inspect R with fieldnames()

fieldnames(typeof(R.a))
> ()
fieldnames(typeof(R.b))
> (::x, ::y)

so inspecting whether the list is empty or not I know if I must recursively call the function.
The problem I don’t know how to sort out is that, when passed R to a function, fieldnames() does not provide the complete names of the variable passed to it

function dale(z)
fieldnames(typeof(z))
end

dale(R)
> (::a,::b)

but in that case, what I get is Symbols, and their length is always 0.

Can on easily get filenames)=, acting on z in function dale(z), to give the whole name of the original argument passed to it (R in dale®), so that I can check the length of the fields?

Thx and sorry for the messy post,

Ferran.


#7

I don’t understand what filenames does.

Did you mean fieldnames? In what way does it not work?

It would be better if you described what you are trying to achieve.


#8

Sorry, yes… it was fieldnames(). Just copied and pasted the wrong thing many times :frowning:


#9

Please accurately define what do you meman by “user-defined struct”. Is a type defined in Base module one? What about one defined in a stdlib package? What about a third party package? Does it only belong to a few packages you are interested in? Does it have to be from a script (i.e. from Main)? Does it have to be a type you defines? Or maybe it could be a type that others could define that satisfy some properties? If so, what are those properties, can you just test those instead?
Is being a struct the only important feature? (i.e. you want to test if it’s a primitive type).

Also related is do you care about the field type or the field value. That determines if you need to handle abstract type.

You see, every single questions I raise above should have a well defined and relatively simple answer that you can easily code out. From your krok, reptil example, however, it’s unclear what about those types are so special that you need to recurse into them. In general, if what makes them special is some general concept of the type, then you just test that after figuring out what it is (see above). Otherwise, you are interested in some property that’s only interesting for you (which is the most common and most useful case BTW) and you need to encode that info yourself, say by either inherit them from the same abstract type, or (possibly combined with abstract type) specializing your “function” (for printing??) on these types.

P.S. are you sure you are not just looking for show?


#10

Too many questions :slight_smile:
I guess, at least for a start, that the data types I will be using are those created by me and only me in my codes, a type defined by me. No packages, no nothing else. So it is the ‘some property that’s only interesting for you (which is the most common and most useful case BTW)’, in your own words…
Thx
Ferran.


#11

I highly doubt if this is actually the property you are interested in, by which I mean, you (the programmer) might be interested in it but in general code (written by you) shouldn’t really care about it’s author. Having your friend fixing a typo in your code will change the (co)author of the code but shouldn’t change the code’s behavior in a way that’s different if you made the change yourself. There’s just no way this can happen with current computer technology.

I should clarify this as “objective property” or “code property”. Anything that your code can react to, rather than anything that is intersting to you for other reason…

I give you a long list since it’s AFAICT the simplest way to guide yourself to the definition of your “user-defined struct”. It’s also to help you to formulate your question in a way that others can actually help. Otherwise there are only guesses and that’s what I’m going to do below too.

The closest interpretation of what you said I can come up with is that you want to treat types in your script differently than anything else. You are not using any packages. In that case, it’s included in my list of questions above and, well, just test if the type is from Main should work for you, i.e. T.name.module === Main. Note that this will break if you move the code. Change Main to @__MODULE__ will work if you put it in a module. It’s still a property that makes no sense to query other than for some low level purposes. In particular, it’s orthogonal to whether the type has field to be iterated, which is what you want to do, so making the decision this way is almost certainly wrong.

If you don’t care too much about the maintainability of the code, you can just hard code the list of special types for you, which is actually a much better method in many ways. OTOH, if you want an answer with a more maintainable approach that makes more sense, you really need a definition of “user-defined struct” that is actually based on some programatically distinguishable properties. Again, the list I have above should be a good place to start.


#12

Thanks for the info but… honestly, you’re making a mess out of the question. The situation I want to solve now is not a general case, it is as simple as the kook() and reptile() case above, defined by me and used by me. Nothing fancy, nothing that should care too many people…


#13

OK?

OK, in that case and if you don’t want to read any one of my potential implementations above, for which I have at least 3, just define your function to treat kook and reptile type differently. That’s as simple as it gets.

Edit: Then update your title please. Your title is very generic. It doesn’t mention kook or reptile specifically. If the example you gave are not examples of a more general question but actually the question itself, please clarify.


#14

I think it could be helpful if you could show us the precise input and corresponding output you’re looking for. Perhaps something along these lines? Otherwise please clarify.

struct Animal
   name::String
   count::Int
end

struct Reptil
   name::String
   parent::Animal
   count::Int
end

struct Krok
   name::String
   age::Int
   parent::Reptil
end

inspect(r, _) = print(r)

function inspect(r::Union{Krok,Reptil,Animal}, padding = 0)
   print(typeof(r))
   for f in fieldnames(typeof(r))
      print("\n" * " "^(padding+2) * "$f = ")
      inspect(getfield(r, f), padding + 2)
   end
end

animals = Animal("All animals", 1_000_000)
reptiles = Reptil("Reptiles", animals, 10_000)
krok = Krok("My crocodile", 42, reptiles)

inspect(krok)

Output:

Krok
  name = My crocodile
  age = 42
  parent = Reptil
    name = Reptiles
    parent = Animal
      name = All animals
      count = 1000000
    count = 10000

#15

That’s why I mentioned show

Also the similarly defined dump


#16

I know, I liked your comment above after you posted it, but thought OP would appreciate the help implementing it, especially the part about customizing behavior for his own types.


#17

Thank you all for the replies…
And Bennedicich, thanks for the code which almost suits a preliminary form of what I’d like to have. Your code works great for the case example but when I remove the

Union{Krok,Reptil,Animal}

form the definition of the function and leave it as

function inspect(r, padding = 0)

the output I get is

Krok
  name = String
  age = Int64
  parent = Reptil
    name = String
    parent = Animal
      name = String
      count = Int64
    count = Int64

z = show(krok)

I removed the Union because I do not want to restrict the function to this case, and I don’t want to list all my structs as I have many… would that have a simple fix?

Thanks again,

Ferran.


#18

Yes there’s a simple fix, by looking at which module the type is defined in. (Again, the answer has already been posted above.) Replace the two inspect methods with:

function inspect(r, padding = 0)
   parentmodule(typeof(r)) ≡ Main || return print(r) # not user-defined

   # user-defined type
   print(typeof(r))
   for f in fieldnames(typeof(r))
      print("\n" * " "^(padding+2) * "$f = ")
      inspect(getfield(r, f), padding + 2)
   end
end

Note: This is just a proof-of-concept. You’ll need to replace Main above with whatever module your types are defined in, and if you have hierarchical modules you’ll need to add support for that.