Proper way to deal with `Vector{Any}` when defining a function

function catstr(arr::Vector{String})
    return reduce(*, arr)
end

# Method-1: OK
arr = ["hello", "world"]
catstr(arr)

# Method-2: Not OK
arr = []
push!(arr, "hello")
push!(arr, "world")
catstr(arr)

Whose fault? Is the function not defined well enough, or is it called improperly?

That’s really up to you. The function is called catstr so it’s actually very reasonable to restrict the input to a collection of Strings like Vector{String}. Then it’s a matter of making sure your inputs fit. Vector{Any} doesn’t fit because you could put non-strings in there. To make an empty Vector{String}, do arr = String[]. That change alone makes your code work again.

If you want to expand what catstr works on, you could change the argument annotation. Removing it entirely is the same as ::Any, which allows many inputs that error at the reduce call. ::Vector would allow vectors containing other things, which could let the reduce call work but in a way that doesn’t fit the name catstr. Off the top of my head, the most liberal annotation fitting the name would be catstr(arr::AbstractArray{T,N} where N) where T<:AbstractString.

1 Like

The above is equivalent to the following.

arr = Any[]
push!(arr, "hello")
push!(arr, "world"

To avoid Vector{Any} do

arr = String[]
push!(arr, "hello")
push!(arr, "world"

If you cannot avoid it, the do:

julia> arr = Any[]
Any[]

julia> push!(arr, "hello")
1-element Vector{Any}:
 "hello"

julia> push!(arr, "world")
2-element Vector{Any}:
 "hello"
 "world"

julia> arr = identity.(arr)
2-element Vector{String}:
 "hello"
 "world"
5 Likes

On a separate note, this is pretty inefficient because every call to * allocates a new string, Better to call join. To implement this sort of function yourself, write to an IOBuffer that you convert to a string at the end.

5 Likes

Thank you all! I’ve learned more than what I asked.

I’d strongly recommend against restricting your functions to just String as SubStrings are also very common and are being excluded.

For example:

str = "Test String"
ss = split(str)
catstr(ss) # errors

You almost always just want to restrict to subtypes of AbstractString

2 Likes

May I ask, is there a hierarchical graph of all types in Julia? Or how can I view the type hierarchy graph involved in the current workspace?

In particular, a more general signature would be function catstr(arr::AbstractVector{<:AbstractString}). (However, you can always broaden the types later.)

1 Like

The entire hierarchy? Are you sure?

julia> using AbstractTrees, TerminalPager

julia> AbstractTrees.children(d::Type) = subtypes(d)

julia> @stdout_to_pager print_tree(Any)
Any
β”œβ”€ AbstractArray
β”‚  β”œβ”€ AbstractRange
β”‚  β”‚  β”œβ”€ LinRange
β”‚  β”‚  β”œβ”€ OrdinalRange
β”‚  β”‚  β”‚  β”œβ”€ AbstractUnitRange
β”‚  β”‚  β”‚  β”‚  β”œβ”€ IdentityUnitRange
β”‚  β”‚  β”‚  β”‚  β”œβ”€ OneTo
β”‚  β”‚  β”‚  β”‚  β”œβ”€ Slice
β”‚  β”‚  β”‚  β”‚  └─ UnitRange
β”‚  β”‚  β”‚  └─ StepRange
β”‚  β”‚  └─ StepRangeLen
β”‚  β”œβ”€ AbstractSlices
β”‚  β”‚  └─ Slices
β”‚  β”œβ”€ ExceptionStack
β”‚  β”œβ”€ LogicalIndex
β”‚  β”œβ”€ MethodList
β”‚  β”œβ”€ ReinterpretArray
β”‚  β”œβ”€ ReshapedArray
β”‚  β”œβ”€ SCartesianIndices2
β”‚  β”œβ”€ WithoutMissingVector
β”‚  β”œβ”€ BitArray
β”‚  β”œβ”€ CartesianIndices
β”‚  β”œβ”€ AbstractRange
β”‚  β”‚  β”œβ”€ LinRange
β”‚  β”‚  β”œβ”€ OrdinalRange
β”‚  β”‚  β”‚  β”œβ”€ AbstractUnitRange
β”‚  β”‚  β”‚  β”‚  β”œβ”€ IdentityUnitRange
β”‚  β”‚  β”‚  β”‚  β”œβ”€ OneTo
β”‚  β”‚  β”‚  β”‚  β”œβ”€ Slice
β”‚  β”‚  β”‚  β”‚  β”œβ”€ StmtRange
β”‚  β”‚  β”‚  β”‚  └─ UnitRange
β”‚  β”‚  β”‚  └─ StepRange
β”‚  β”‚  └─ StepRangeLen
β”‚  β”œβ”€ BitArray
β”‚  β”œβ”€ ExceptionStack

Maybe a few smaller selections might help.

julia> print_tree(AbstractString)
AbstractString
β”œβ”€ LazyString
β”œβ”€ LazyString
β”œβ”€ String
β”œβ”€ SubString
└─ SubstitutionString

julia> print_tree(Number)
Number
β”œβ”€ MultiplicativeInverse
β”œβ”€ Complex
└─ Real
   β”œβ”€ AbstractFloat
   β”‚  β”œβ”€ BigFloat
   β”‚  β”œβ”€ Float16
   β”‚  β”œβ”€ Float32
   β”‚  └─ Float64
   β”œβ”€ AbstractIrrational
   β”‚  └─ Irrational
   β”œβ”€ Integer
   β”‚  β”œβ”€ Bool
   β”‚  β”œβ”€ Signed
   β”‚  β”‚  β”œβ”€ BigInt
   β”‚  β”‚  β”œβ”€ Int128
   β”‚  β”‚  β”œβ”€ Int16
   β”‚  β”‚  β”œβ”€ Int32
   β”‚  β”‚  β”œβ”€ Int64
   β”‚  β”‚  └─ Int8
   β”‚  └─ Unsigned
   β”‚     β”œβ”€ UInt128
   β”‚     β”œβ”€ UInt16
   β”‚     β”œβ”€ UInt32
   β”‚     β”œβ”€ UInt64
   β”‚     └─ UInt8
   └─ Rational
4 Likes

Thanks! Very helpful! But what does this mean?

It’s explained in the documentation of AbstractTrees.jl:
https://juliacollections.github.io/AbstractTrees.jl/stable/

Using this package involves implementing the abstract tree interface which, at a minimum, requires defining the function AbstractTrees.children for an object.

By defining how to get all children from a Type object (= simply look up its subtypes), we can immediately print the whole type tree β€œunterneath” (or β€œabove” ? :sweat_smile: ) the given type.

1 Like

This is a short hand notation for a function definition.

using AbstractTrees

function AbstractTrees.children(d::Type)
    return subtypes(d)
end

That in turn is also a short hand for the following.

import AbstractTrees: children

function children(d::Type)
    return subtypes(d)
end

We extend the method children from AbstractTrees for Type such that it maps to InteractiveUtils.subtypes.

1 Like

Oh! I see. Thanks! @mkitti @Sevi

1 Like