Writing function that flexibly accepts string, string array, tuple, and others as argument

I am having trouble figuring out the recommended way to write a function that flexibly accepts any argument that can be converted into an array of strings. As a simple example, say I have the following function that simply prints each element of the array on its own line:

function printArrayLines(textStringArray::Array{String,1})

    # convert any 1-element strings into an Array
    x = convert(Array{String,1},textStringArray)
    
    for n in eachindex(x)
        println(x[n])
    end
end

Only the first few test cases shown below will work. What is the best way to program for all the below test cases?

## test code
# arrays work
printArrayLines(["First","Second","Third"])
printArrayLines(["First"])
# strings fail
printArrayLines("First")
# multiple arguments fail
printArrayLines("First","Second","Third")
# tuples fail
printArrayLines(("First"))
printArrayLines(("First","Second","Third"))
# including numbers in the array fails
printArrayLines(["First",2,"Third"])

A few thoughts:

  1. It is common in Matlab and some other languages to write a single function that operates on both scalars and arrays, but this is not common practice in Julia, both because it is not necessary for efficiency and because we have nice .-call syntax for calling a scalar method on a collection of elements.
  2. If you truly don’t know or care about the exact types of the inputs to your function, there is no need to put any type restriction on them at all. The function definitions f(x::Vector{String}) = ... and f(x) = ... will have exactly the same performance in most cases.

With that in mind, perhaps we can rewrite your function to always operate on a scalar:

function printLine(element)
  println(x)
end

Having done that, it seems like maybe this function doesn’t even need to exist: it’s just a new name for the existing println function. In fact, println with .-calls already seems like it totally solves the problem:

julia> println.(["First", "Second", "Third"])
First
Second
Third
3-element Array{Nothing,1}:
 nothing
 nothing
 nothing

julia> println.(["First"])
First
1-element Array{Nothing,1}:
 nothing

julia> println("First")
First

julia> println.(("First", "Second", "Third"))
First
Second
Third
(nothing, nothing, nothing)

julia> println.(("First", 2, "Third"))
First
2                                                                                                                                                                                                                                                                                                                                     
Third                                                                                                                                                                                                                                                                                                                                 
(nothing, nothing, nothing)                                                                                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                                                                                                                      
julia> println.(["First", 2, "Third"])
First
2
Third
3-element Array{Nothing,1}:
 nothing
 nothing
 nothing
3 Likes

While what is said above is probably the right answer in this case, you sometimes have to deal with different input types and do some conversions. For that it’s quite common to have a main generic method and then to add a few shorts ones to handle the special cases. For your example I would do something like this:

function printArrayLines(strings)    
    for s in strings
        println(s)
    end
end
printArrayLines(s::String)  = printArrayLines([s])
printArrayLines(strings...) = printArrayLines(strings)
3 Likes

I come from an R background and the brain is slowly steering towards the Julia mindset. This post is tremendously helpful for me; I need to think scalar first and then expand. The issue that will still remain here for me is that I want to do more inside the for loop than just print the line. I think @jonathanbieler is showing the programming idiom I am after so that within the for-loop, I can manipulate each element of the array. Thanks again for the quick reply!!