Type Sudoku (test if object is a struct)

This is NOT to complain about Julia, rather a learning experience, so bear with me…

Dispatching to a struct type seems like the match of the last resort - after a lot of not this, not that, not really testable. And contrary to Sudoku, one never knows if it can be solved.

I got great help here putting multiple dispatch to work, still think a test if an object is a structure is necessary.

The following serializer code iterates through an object’s hierarchy and whenever it sees homogeneous data, writes it out. The prefixes with type and dimension data are omitted here.
I feel this can be made better.

function isstruct(v) 
    if applicable(fieldnames, typeof(v)) 
        (fieldcount(typeof(v)) > 0) && !(typeof(v)<:Tuple) # for Array_of_Any
    else
        false
    end
end

Writables = Union{<:Real, Char, String, 
               AbstractArray{<:Real}, 
               AbstractArray{Char}, 
               AbstractArray{String}}

function serial(v)
    tv = typeof(v)
    if tv <: Writables
        println(v)
        println("---")
    elseif isstruct(v)
        println("(Struct)")
        for name in fieldnames(tv)
            println("$(name)")
            serial(getfield(v, name));
        end
    elseif tv<:AbstractArray || tv <: Tuple
        println("(Array/Tuple)")
        v1 = first(v)
        if isstruct(v1) 
            println("(Struct)")
            for name in fieldnames(typeof(v1))
                println("$(name)")
                serial(getfield.(v, name));
            end
            # todo: array of homogeneous tuple
        else
            serial.(v);
        end
    else
        error("unknown type: $tv")
    end
end

# Test data
Array_of_Int = [1 ,2]
Array_of_Tuple = [(1, 2), (2, 3)]
Array_of_Any = [(1, 2), "Ab"]

mutable struct Coords
    x::Float64
    y::Float64
    z::Float64
end
Coords() = Coords(rand(), rand(), rand())
Array_of_Struct = [Coords() for i in 1:5]
Single_Struct = Coords()

# Test
println("Test Array_of_Int")
serial(Array_of_Int)
println()
println("Test Array_of_Tuple")
serial(Array_of_Tuple)
println()
println("Test Array_of_Any")
serial(Array_of_Any)
println()
println("Test Single_Struct")
serial(Single_Struct)
println()
println("Test Array_of_Struct")
serial(Array_of_Struct)

Output:

Test Array_of_Int
[1, 2]
---

Test Array_of_Tuple
(Array/Tuple)
(Array/Tuple)
1
---
2
---
(Array/Tuple)
2
---
3
---

Test Array_of_Any
(Array/Tuple)
(Array/Tuple)
1
---
2
---
Ab
---

Test Single_Struct
(Struct)
x
0.9220727028581359
---
y
0.6295692430403457
---
z
0.023560089621190716
---

Test Array_of_Struct
(Array/Tuple)
(Struct)
x
[0.834535060962323, 0.340497217736315, 0.7824763128066028, 0.8189685607684121, 0.9859094096185965]
---
y
[0.4761584369317642, 0.4352515300190545, 0.14527270184939045, 0.6024455919678469, 0.6632760955815888]
---
z
[0.28120117129446975, 0.19801990106458578, 0.03265890208397537, 0.6550273380547817, 0.04383975637217996]
---

I think the issue is that a MATLAB struct is not the same thing as a Julia struct. The Julia struct keyword is more closely related to MATLAB’s classdef. And most things will be structs in Julia.

2 Likes

https://docs.julialang.org/en/v1/manual/types/#Composite-Types

Composite types are introduced with the struct keyword followed by a block of field names

almost everything you see that are not in Base or some low level package are gonna be struct type.

Yes sure it is not, and almost everything in Julia is a struct.
I have tried to understand how the established Julia serializer packages traverse the object tree.
Was then a too big jump in complexity. If we want to become Julia more accepted (especially by the Matlab folks), we have to fill exactly these gaps.

what gaps? From what I can tell, Julia is much more flexible and expressive than MATLAB. You don’t want to re-invent stuff to go back to MATLAB

Yes, and can easily hard coded for a small number of known objects.
The problem here is the general case. Given an arbitrary Julia object, traverse and print all data.

doesn’t sound like something well defined but you can get most job done with:

Basically you teach it what’s “children” given an object, and it will keep going traversing

No no! The gap is not with Julia. There is a gap for people transiting from Matlab to Julia. They (we) are often not trained programmers and experience a kind of complexity shock.
There is no intention to model Julia back to Matlab. Just help other people with another, or simply less background.

The job of a serializer should be defined well enough. In the example and test results the focus was on type detection.
But yes, specific background was not given.
Besides from learning Julia, I intend to use the serializer for inter-process communication (Matlab<>Julia).
The data structure is relatively simple, some arrays or structs with arrays, each 10e8 double.
Some differences in the type system between Matlab and Julia need to be bridged.
There is the very good MAT.jl package taking more or less care of this. But relying on .mat files means a canned save command from Matlab, and the saving time is already dominant… Since there is no fopen, a pipe cannot be used.

If the goal is to have a way to create an object in Julia and write it to a MATLAB struct, can I suggest Dict?

I think isstruct may be written more concisely as

isstruct1(obj::T) where {T} = !isprimitivetype(T)

isstruct1(obj::Union{Array,Tuple,String}) = false

Also has benefit of extensibility if you decide add custom dispatch rules for other types.

1 Like

I can only guess, but have no idea how this can help in efficiently serializing and storing/streaming data.
Reading back (look up) would be only 1x. We talk about 50 or so large arrays organized in structs.

Thanks a lot!
Just found nfields(), have to compare. In the past I had some surprising false positives.
As said, more structs than you think.

nfields must be more efficient than applicable as well. The latter has to search the method table in runtime while the result of the former is a compile-time constant. As such, the compiler might infer it and eliminate some branches in the serialization function.

1 Like

Probably no other language has such a large bandwidth from highest (Metaprogramming.jl, Symbolics.jl, Modia.jl) to these levels where the programmer can assist the compiler.

Thanks for sharing this background insight, see also this and this.

I could replace all

if typeof(v) == …

by a suitable dispatch function

fun(v::T) where {T…

remaining what I called “the match of the last resort”.
nfields() did not cut it in the end because hitting Tuple. Your isstruct version works as expected

function serial(v)
    if isstruct(v)
        ...
    else
        error("unsuitable type")
    end
end

Since isstruct() is evaluated at compile time, looks like we are done.

I mean introducing this correspondence

Matlab:

struct with fields:
a: 1
b: "lol"

<=>

Julia:

Dict{Symbol, Any} with 2 entries:
:a => 1
:b => "lol" 

Because the Dict is the closest Julia type to Matlab’s struct, a one-off container with named elements.

Dicts look close on the disp/show level, but their call looks different: s[a] vs. s.a for a named tuple, or?

That dissimilarity is much more superficial than the difference between M struct and J struct. But sure, a named tuple would work too, it all depends on how you want it to be treated in Julia.

1 Like

Just for the recall, I just had a need for such a test, and I found isstructtype in Base which seems dedicated to that (at least in 1.7.1)