4
votes

I'm trying to create flexible function that would allow variety of uses:

save(image_url = "http://...")
save(image_url = "http://...", title = "Cats", id = "cats")
save(text = "Some text", comments = "Some comments")
save(text = "Another text", title = "News", compression = true)

Basically it's 3 (or more) functions (save_image_url, save_image_path, save_text) combined .

All of them have 3 same optional arguments title, descriptions, compression. And each can have any number of specific arguments.

I don't want to use positional arguments because it would be hard to remember the order of arguments. Also, the first argument would have the same String type for image_url and image_path and text.

Unfortunately it seems multiple dispatch is not working on named arguments. What are the patterns to handle such cases then?

Not working implementation

function save(;
  image_url::   String,

  title::       Union{String, Nothing} = nothing,
  description:: Union{String, Nothing} = nothing,
  compression:: Union{Bool, Nothing}   = nothing
)::Nothing
  nothing
end


function save(;
  image_path::  String,

  title::       Union{String, Nothing} = nothing,
  description:: Union{String, Nothing} = nothing,
  compression:: Union{Bool, Nothing}   = nothing
)::Nothing
  nothing
end

function save(;
  text::        String,
  comments::    Union{String, Nothing} = nothing,

  title::       Union{String, Nothing} = nothing,
  description:: Union{String, Nothing} = nothing,
  compression:: Union{Bool, Nothing}   = nothing
)::Nothing
  nothing
end
2
Unfortunately, keyword arguments do not participate in dispatch. the reason for this is that it was judged as likely to give a combinatorial explosion in methods if you use a bunch of keyword arguments.Mason
For instance, f(;a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8) would have 9! = 362880 different methods. Yikes!Mason

2 Answers

7
votes

Multiple dispatch is great, but it sometimes is the wrong tool for the job. In this case, basic dispatch on function names is simple and sufficient. I would prefer this option if at all possible. However, if a consistent function name is necessary, then you can always dispatch on custom types.

Different Function Names

function save_image_url(url::String; kwargs...)
    handle_general_options(; kwargs...)
    # url specific code
end

function save_image_path(path::String; kwargs...)
    handle_general_options(; kwargs...)
    # path specific code
end

function handle_general_options(; title=nothing, description=nothing)
    # shared code
end

Custom Types

struct ImageURL
   val::String
end

function save(url::ImageURL; kwargs...)
    handle_metadata(; kwargs...)
    # url specific code
end

function handle_general_options(; title=nothing, description=nothing)
    # shared code
end

You can make dispatch clear at the call site by immediately creating the object to dispatch on:

save(ImageURL("https://..."); title="SomeTitle")
6
votes

I would suggest splitting the user interface and implementation details like this:

function save(;
    image_url=nothing,
    image_path=nothing,
    text=nothing,

    title=nothing,
    description=nothing,
    compression=nothing
)
    save(image_url, image_path, text, title, description, compression)
end

function save(image_url::String, ::Nothing, ::Nothing, title, description, compression)
    # do something
end

function save(::Nothing, image_path::String, ::Nothing, title, description, compression)
    # do something
end

function save(::Nothing, ::Nothing, text::String, title, description, compression)
    # do something
end

function save(image_url, image_path, text, title, description, compression)
    @error "only one of image_url, image_path, text are acceptable"
end

When things become too complicated, you can always create a new struct from keyword arguments and dispatch it by using traits.