Julia function to select struct member based on the caller function

Viewed 104

I'm trying to implement a function that I simplified as follows:

struct DummyStruct
    x::Int64
    y::Int64
    z::Int64
end

function match_data_from_dummy_struct(dummy_struct::Vector{DummyStruct}, value::Int64)
    # Long processing that includes:
        # If called from match_data_x, match value with dummy_struct.x
        # If called from match_data_y, match value with dummy_struct.y
        # If called from match_data_z, match value with dummy_struct.z
end

function match_data_x()
    # Call to match_data_from_dummy_struct
end

function match_data_y()
    # Call to match_data_from_dummy_struct
end

function match_data_z()
    # Call to match_data_from_dummy_struct
end

The ugly way to do this is to have three versions of the same match_data_from_dummy_struct:

  1. match_data_from_dummy_struct_x: to match with DummyStruct.x
  2. match_data_from_dummy_struct_y: to match with DummyStruct.y
  3. match_data_from_dummy_struct_z: to match with DummyStruct.z

Another option is to pass a numeric value to match_data_from_dummy_struct, say if it's 1 then only match with x, if it's 2 then only match with y, if it's 3 then only match with z.

Is there a more clever / readable way to implement this?

1 Answers

Generally, always the best approach is to use the standard multiple dispatch model.

However if for some reasons you want to control it more programmatically you can create a concrete subtype of Var and dynamically dispatch on field name:

function do_dummy(d::DummyStruct, ::Val{T}) where T
    getfield(d, T) + 1
end

This can be used as:

julia> d = DummyStruct(10,20,30)
DummyStruct(10, 20, 30)

julia> do_dummy(d, Val{:x}())
11

julia> do_dummy(d, Val{:z}())
31

Note that this might be very tempting to just use the field Symbol as an argument but this will result in worse performance. Let us consider identical functionality provided by:

function do_dummy(d::DummyStruct, field::Symbol)
    getfield(d, field) + 1
end

Let us compare the assembly code generated for both versions of do_dummy.

For the Val{:x}() everything just gets nicely compiled down to integer addition:

julia> @code_native do_dummy(d, Val{:x}())
        .text
; ┌ @ REPL[51]:1 within `do_dummy`
        pushq   %rbp
        movq    %rsp, %rbp
; │ @ REPL[51]:2 within `do_dummy`
; │┌ @ int.jl:87 within `+`
        movq    (%rcx), %rax
        incq    %rax
; │└
        popq    %rbp
        retq
        nopl    (%rax)
; └

For the Symbol version things are not nice any more:

julia> @code_native do_dummy(d, :x)
        .text
; ┌ @ REPL[52]:1 within `do_dummy`
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   %rsi
        pushq   %rdi
        pushq   %rbx
        subq    $72, %rsp
        movq    %rdx, %rdi
        movq    %rcx, %rbx
        vxorps  %xmm0, %xmm0, %xmm0
        vmovaps %xmm0, -48(%rbp)
        movq    $0, -32(%rbp)
        movl    $jl_get_pgcstack, %eax
        callq   *%rax
        movq    %rax, %rsi
; │ @ REPL[52]:2 within `do_dummy`
        movq    $4, -48(%rbp)

[another 50 lines of assembly omitted for readability]
Related