# How to dispatch this serializer function?

**URL:** https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981
**Category:** Performance
**Tags:** dispatch
**Created:** [August 7, 2021, 6:17am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981 "2021-08-07T06:17:12Z")
**Posts on this page:** 18
**Page:** 2

<div class="post-metadata">

### Author: ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)
#### Post date: [August 7, 2021, 1:34pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/21 "2021-08-07T13:34:48Z")

</div>

Yeah that’s not an issue. An array is just a struct.

---

<div class="post-metadata">

### Author: ![Skoffer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skoffer/32/378_2.png) [@Skoffer](https://discourse.julialang.org/u/Skoffer)
#### Post date: [August 7, 2021, 7:44pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/22 "2021-08-07T19:44:09Z")

</div>

I haven’t followed discussions later in this thread, so sorry if I repeat something.

Returning to this snippet:

```julia
    # Just serialize each member.
    for ii = 1:numel(v)
        write(io, serializej2m(v[ii]))
    end

```

The problem of these lines is that type `v[ii]` can be unknown at the time of compilation, if for example `v` is defined as `v = Any[1, 2, 3, 4]`.In this case, all dispatching will happen in runtime and this is bad, cause it’s relatively slow. The whole thing about type-stability is that if the compiler can calculate types during compile time, then it can write highly efficient code. If not then things can get ugly.

Union splitting is one way to overcome this problem, if you know possible set of types beforehand, you can write

```julia
...
if v[ii] isa UInt8
  write(io, serializej2m(v[ii]))
elseif v[ii] isa UInt16
  write(io, serializej2m(v[ii]))
...

```

What is going on here, is that during runtime, instead of running full runtime lookup of type and corresponding function, it will just make a pointer comparison (which is fast) and execute the corresponding branch.

Now, coincidently this code looks similar to the `classToByte` function in your original definition, but the thing is, it is used in different circumstances. If all you are trying to do is to dispatch (i.e. choose function) depending on the type of incoming object, then you should use multiple dispatch, because compiler can do it better then you. If you are in a situation when there is no way to avoid runtime dispatch, it may be useful to use union splitting.

With all that said, maybe it is not applicable in this situation, since I do not quite understand in which situation should `serializej2m(io, v::AbstractArray)` be used, so maybe union splitting is not applicable here.

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 9, 2021, 8:33am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/23 "2021-08-09T08:33:53Z")

</div>

Your info comes handy to handle the single/array dispatch:

```julia
# Encode number types
utype(t) = Union{t, AbstractArray{t}}
type2byte(::T) where {T<:utype(Float64)} = UInt8(0)
type2byte(::T) where {T<:Union{Any, AbstractArray}} = UInt8(255) # fallback for struct

function _serialize_old(io, v::T) where {T<:Real} 
    println("processing 'single Real'")
    write(io, type2byte(v))
    write(io, UInt8(0))
    write(io, v)
end
function _serialize_old(io, v::T) where {T<:AbstractArray{<:Real}}
    println("processing 'Array of Real'")
    write(io, type2byte(v))
    write(io, UInt8(ndims(v)))
    write.(Ref(io), UInt32.(collect(size(v))))
    write(io, v)
end

```

Tried to merge into

```julia
function _serialize(io, v::T) where {T<:utype(Real)} 
    write(io, type2byte(v))
    if v isa Real
        println("isa 'single Real'")
        write(io, type2byte(v))
        write(io, UInt8(0))
    else
        println("isa 'Array of Real'")
        write(io, UInt8(ndims(v)))
        write.(Ref(io), UInt32.(collect(size(v))))
    end
    write(io, v)
end

```

works for

```julia
julia> serializej2m(1.0);
isa 'single Real'

```

but not for

```julia
julia> serializej2m([1.0])
processing 'Array of Struct'
ERROR: no components found in type Vector{Float64}

```

The union in the dispatch apparently confuses the type, branching to

```julia
function _serialize(io, v::T) where {T<:AbstractArray}
    println("processing 'Array of Struct'")

```

If there is no obvious flaw, I will open another thread to learn about type hierarchy beyond the often depicted number types.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [August 9, 2021, 9:15am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/24 "2021-08-09T09:15:24Z")

</div>

> [@Bardo](#):
>
> `UInt32.(collect(size(v)))`

Just write:

```julia
UInt32.(size(v))

```

Broadcasting works with tuples too. You should basically never use `collect`, unless your code cannot work without it.

Here you actually don’t need to broadcast `write` at all, since it accepts multiple inputs:

```julia
write(io, UInt32.(size(v))...)

```

Still, looking at your latest code, why do you have to branch on scalar vs array? Didn’t my suggestion to use

```julia
write(io, prefix(v)...)
write(io, v)

```

work for both?

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 9, 2021, 9:31am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/25 "2021-08-09T09:31:29Z")

</div>

I yet need to give different dimension info:

```julia
write(io, UInt8(0))

```

vs.

```julia
write(io, UInt8(ndims(v)))
write(io, UInt32.(size(v))...)

```

and not all objects allow _ndims(v)_ to test inside a function.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [August 9, 2021, 9:43am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/26 "2021-08-09T09:43:51Z")

</div>

Sure, but the `dimensions` function (called by `prefix` in my example) should handle that, no?

It seems to me that the logic is in the wrong place. Everything related to creating metainformation, like the type tag and dimensions, should be done inside a function like `prefix` (or maybe call it `metainfo` or something), and then `_serialize` just prints the metainfo and then the data? It seems cumbersome to put the branching logic inside the printing function like that.

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 9, 2021, 10:13am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/27 "2021-08-09T10:13:12Z")

</div>

I am with you to put such information into a function, still the work has to be done [somewhere](http://blog.wisefaq.com/wp-content/uploads/2008/05/amoh-smallv3.jpg).

```julia
prefix(v) = (type2byte(v), dimensions(v)...)

```

I just did not know how to write a function like ndims(v) which [works for every object](https://lispcast.com/what-is-a-total-function/).  
Only recently I learned applicable(), so why not something like

```julia
nd(t) = applicable(ndims, t) ? ndims(t) : 0

```

No idea if there is a speed penalty though.

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [August 9, 2021, 10:55am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/28 "2021-08-09T10:55:23Z")

</div>

> [@Bardo](#):
>
> still the work has to be done [somewhere](http://blog.wisefaq.com/wp-content/uploads/2008/05/amoh-smallv3.jpg).

Yes. The point of splitting the work into smaller tasks that are handled separately is that it becomes much simpler, and you avoid a lot of comparisons/tests and nested branching that tend to happen if you collect the logic in single place.

I suggested an implementation for `dimensions` previously returned the number of dimensions and the length of each:

```julia
dimensions(x) = (UInt8(2), UInt32(1), UInt32(length(x))) # works for numbers, chars and strings
dimensions(x::AbstractArray) = (UInt8(max(2, ndims(x))), UInt32.(size(x))...)

```

The implementation may not be correct anymore, since it seems like you now allow 0-dimensionality.

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 9, 2021, 11:08am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/29 "2021-08-09T11:08:18Z")

</div>

Right, now I encode the Julia dimensions and, if \>0, the size.  
Nice to see UInt32 avoids the need to join tuples.

But _length()_ neither works with every object, for example a single struct.  
The dimensions function then likely needs some branching using _applicable()_, or?

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [August 9, 2021, 11:25am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/30 "2021-08-09T11:25:52Z")

</div>

I may have missed something up-thread, but I don’t fully know how general you want this serialization function to be. The `dimensions` function I suggested is divided in two methods: one for AbstracArrays and one for the rest, which I assumed would be basic `Number`s, `Char`s and `String`s. I guess you want something more general?

Is everything either `AbstractArray` or scalar, or could there be other container types? Do you have a rule/list for what sort of data structures you want to cover? Depending on the scope, it might be possible to solve everything with dispatch. But perhaps not.

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 9, 2021, 12:40pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/31 "2021-08-09T12:40:49Z")

</div>

Thanks indeed for your patience!

Here is the list with the working dispatch, but without factoring out the prefix part:

```julia
single number: <:Real => UInt8(0)
array of number: <:AbstractArray{<:Real} => UInt8(ndims(v)), UInt32(size(v))
single char: ::Char => UInt8(0)
array of char: <:AbstractArray{Char} => UInt8(ndims(v)), UInt32(size(v))
single string: ::String => UInt8(1), UInt32(length(v))
array of string: <:AbstractArray{String}} => UInt8(ndims(v)), UInt32(size(v))
single tuple: ::Tuple => UInt8(1), UInt32(length(v))
array of tuple <:AbstractArray{Tuple} => UInt8(ndims(v)), UInt32(size(v))  
single struct <:Any => UInt8(0) 
array of struct <:AbstractArray => UInt8(ndims(v)), UInt32(size(v))

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [August 9, 2021, 1:30pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/32 "2021-08-09T13:30:11Z")

</div>

I think this reduces to

```julia
dimensions(x) = UInt8(0)
dimensions(x::Union{String, Tuple}) = (UInt8(1), UInt32(length(x)))
dimensions(x::AbstractArray) = (UInt8(ndims(x)), UInt32.(size(x))...)

```

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 9, 2021, 1:41pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/33 "2021-08-09T13:41:00Z")

</div>

[quote=“DNF, post:28, topic:65981”]

```julia
You've been faster ;-) - thanks a LOT!
```

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 10, 2021, 2:55pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/34 "2021-08-10T14:55:12Z")

</div>

Related subject continued in a new [thread](https://discourse.julialang.org/t/type-sudoku-test-if-object-is-a-struct/66139).

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 11, 2021, 2:59pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/35 "2021-08-11T14:59:55Z")

</div>

Added above my final version. Thx again for your help!

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 22, 2021, 9:56pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/36 "2021-08-22T21:56:57Z")

</div>

Still valid, but run into problems when trying to write it.  
write lacks a method for tuples, adding one like

```julia
import Base.write
function write(io, T::Tuple)
    for i = 1:length(T)
        write(io, T[i])
    end
end

```

creates an error

```julia
ERROR: LoadError: MethodError: write(::IOStream, ::Tuple{UInt8, UInt32, UInt32}) is ambiguous. Candidates:
  write(io, T::Tuple) in Main at c:\Users\bardo\MATLAB Drive\serialize_10.jl:16
  write(io::IO, x) in Base at io.jl:635
  write(io::IO, x1, xs...) in Base at io.jl:636
Possible fix, define
  write(::IO, ::Tuple)

```

---

<div class="post-metadata">

### Author: ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)
#### Post date: [August 22, 2021, 10:25pm UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/37 "2021-08-22T22:25:56Z")

</div>

The error message says that it cannot decide which method to choose (it’s ambiguous). `write(io, T::Tuple)` matches the second argument, and `write(io::IO, x)` matches the first argument. So which method should it choose for `write(io::IO, x::Tuple)`?

So it suggests that you should define a method definition for `write(io::IO, x::Tuple)`. So add `::IO` to the method you defined.

But I suggest this definition instead of the loop:

```julia
Base.write(io::IO, x::Tuple) = write(io::IO, x...)

```

---

<div class="post-metadata">

### Author: ![Bardo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bardo/32/21601_2.png) [@Bardo](https://discourse.julialang.org/u/Bardo)
#### Post date: [August 23, 2021, 5:24am UTC](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981/38 "2021-08-23T05:24:58Z")

</div>

Great help, great forum! Thx.

[Previous page](https://discourse.julialang.org/t/how-to-dispatch-this-serializer-function/65981.md?page=1)
