structjuliamultiple-dispatch

Multiple dispatch for the constructor method in Julia


I have written a struct and I want to have a constructor method that supports both vector and tuple input for the argument barrierPositions.

struct MapInfo
    mapSize::Tuple{Int64, Int64}
    flagPosition::Tuple{Int64, Int64}
    barrierPositions::Vector{Tuple{Int64, Int64}}

    function MapInfo(;mapSize::Tuple{Int64, Int64}, flagPosition::Tuple{Int64, Int64}, 
        barrierPositions::Vector{Tuple{Int64, Int64}})
        unique!(barrierPositions)
        deleteat!(barrierPositions, findall(x->x==flagPosition, barrierPositions))
        return new(mapSize, flagPosition, barrierPositions)
    end

    function MapInfo(;mapSize::Tuple{Int64, Int64}, flagPosition::Tuple{Int64, Int64}, 
        barrierPositions::Tuple{Int64, Int64})
        return MapInfo(mapSize=mapSize, flagPosition=flagPosition, barrierPositions=[barrierPositions])
    end
end

But if I run the following command, it seems to overlook my first constructor method which should receive vectors for the argument barrierPositions.

mapInfo = MapInfo(mapSize=(4,4), flagPosition=(3,3), barrierPositions=[(3,2), (2,3)])
ERROR: TypeError: in keyword argument barrierPositions, expected Tuple{Int64, Int64}, got a value of type Vector{Tuple{Int64, Int64}}
Stacktrace:
 [1] top-level scope
   @ e:\Master Thesis\lu_jizhou\Learning\DQN.jl:250

How can I do this?


Solution

  • Julia does not do dispatch on keyword arguments, only on the positional arguments (before ;). Typically, if you want such alternative inputs it's often better to use outer constructors.

    An example:

    struct MapInfo
        mapSize::Tuple{Int64, Int64}
        flagPosition::Tuple{Int64, Int64}
        barrierPositions::Vector{Tuple{Int64, Int64}}
    
        function MapInfo(mapSize::Tuple{Int64, Int64}, flagPosition::Tuple{Int64, Int64}, 
                         barrierPositions::Vector{Tuple{Int64, Int64}})
            unique!(barrierPositions)
            deleteat!(barrierPositions, findall(x->x==flagPosition, barrierPositions))
            return new(mapSize, flagPosition, barrierPositions)
        end
    
    end
    
    
    function MapInfo(;mapSize::Tuple{Int64, Int64}, flagPosition::Tuple{Int64, Int64}, 
                     barrierPositions::Tuple{Int64, Int64})
        return MapInfo(mapSize, flagPosition, [barrierPositions])
    end
    

    Note that the inner constructor is not by keywords in this example. The outer constructor is keyword based, but you can't have more than one constructor without positional arguments, since dispatch is done on positional arguments only. (Two methods with no positional arguments are the same method, so the last one will replace the first).

    If you need more constructors, you must have different positional arguments. I.e. remove the ; in the formal argument list.

    There's no magic in using an outer constructor, you could as well use an inner constructor. Inner constructors are typically used if there are tests and consistency checks. You could write it as an inner constructor where you call new instead of MapInfo, and avoid the unique!, which is unnecessary with a single Tuple (and simplify the deletat!/findall logic).