Inconsistency between map and fmap

Hi,
I saw fmap as an extension of map to functors and I want to use it convert type of element of arrays that are leaves of some struct:
Taking the doc example:

julia> using Functors

julia> struct Foo
       x
       y
     end

julia> @functor Foo

julia> model = Foo(1, [1, 2, 3])
Foo(1, [1, 2, 3])

julia> fmap(float, model)
Foo(1.0, [1.0, 2.0, 3.0])

julia> fmap(Float32,model)
ERROR: MethodError: no method matching Float32(::Vector{Int64})

Closest candidates are:
(::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number}
 @ Base char.jl:50
(::Type{T})(::Base.TwicePrecision) where T<:Number
 @ Base twiceprecision.jl:266
(::Type{T})(::Complex) where T<:Real
 @ Base complex.jl:44
...

Stacktrace:
[1] (::Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)})(::Function, ::Vector{Int64})
  @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:106
[2] (::Functors.CachedWalk{Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)}, Functors.NoKeyword})(::Function, ::Vector{Int64})
  @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:146
[3] (::Functors.var"#recurse#19"{Functors.CachedWalk{Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)}, Functors.NoKeyword}})(xs::Vector{Int64})
  @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:37
[4] map
  @ ./tuple.jl:274 [inlined]
[5] map(::Function, ::NamedTuple{(:x, :y), Tuple{Int64, Vector{Int64}}})
  @ Base ./namedtuple.jl:219
[6] _map(f::Function, x::NamedTuple{(:x, :y), Tuple{Int64, Vector{Int64}}})
  @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:1
[7] (::Functors.DefaultWalk)(::Function, ::Foo)
  @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:76
[8] ExcludeWalk
  @ ~/.julia/packages/Functors/rlD70/src/walks.jl:106 [inlined]
[9] CachedWalk
  @ ~/.julia/packages/Functors/rlD70/src/walks.jl:146 [inlined]
[10] execute(::Functors.CachedWalk{Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)}, Functors.NoKeyword}, ::Foo)
  @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:38
[11] fmap(::Type, ::Foo; exclude::Function, walk::Functors.DefaultWalk, cache::IdDict{Any, Any}, prune::Functors.NoKeyword)
  @ Functors ~/.julia/packages/Functors/rlD70/src/maps.jl:11
[12] fmap(::Type, ::Foo)
  @ Functors ~/.julia/packages/Functors/rlD70/src/maps.jl:3
[13] top-level scope
  @ REPL[59]:1

However if we use map we have

julia> r = [1,2,3];

julia> map(Float32, r)
3-element Vector{Float32}:
 1.0
 2.0
 3.0

julia> fmap(Float32, r)
ERROR: MethodError: no method matching Float32(::Vector{Int64})

Closest candidates are:
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number}
   @ Base char.jl:50
  (::Type{T})(::Base.TwicePrecision) where T<:Number
   @ Base twiceprecision.jl:266
  (::Type{T})(::Complex) where T<:Real
   @ Base complex.jl:44
  ...

Stacktrace:
 [1] (::Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)})(::Function, ::Vector{Int64})
   @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:106
 [2] (::Functors.CachedWalk{Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)}, Functors.NoKeyword})(::Function, ::Vector{Int64})
   @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:146
 [3] execute(::Functors.CachedWalk{Functors.ExcludeWalk{Functors.DefaultWalk, DataType, typeof(Functors.isleaf)}, Functors.NoKeyword}, ::Vector{Int64})
   @ Functors ~/.julia/packages/Functors/rlD70/src/walks.jl:38
 [4] fmap(::Type, ::Vector{Int64}; exclude::Function, walk::Functors.DefaultWalk, cache::IdDict{Any, Any}, prune::Functors.NoKeyword)
   @ Functors ~/.julia/packages/Functors/rlD70/src/maps.jl:11
 [5] fmap(::Type, ::Vector{Int64})
   @ Functors ~/.julia/packages/Functors/rlD70/src/maps.jl:3
 [6] top-level scope
   @ REPL[52]:1

Is there any reason why fmap has a different behaviour than map when applied to Arrays?
How can I do that otherwise?
Thanks,

Ferréol

It definitely seems that way from Functors.jl home page’s link to the wikipedia article on functors in a functional programming context.

But it appears that fmap hits a limit here because Array isn’t a struct with finite fields to operate over, so float([1, 2, 3]) is being called, not float(1) etc. Float32([1, 2, 3]) fails because there’s no method defined for this call.

Just in case, also know that “functors” is a common name for callable instances in base Julia, it’s a different concept entirely.

Thank you for your rapid answer.

But it appears that fmap hits a limit here because Array isn’t a struct with finite fields to operate over, so float([1, 2, 3]) is being called, not float(1) etc. Float32([1, 2, 3]) fails because there’s no method defined for this call.

IMO it is more a bug than a feature, should I propose a fallback such as:

function fmap(F, x)
    try
        fmap(F,  x)
    catch
        map(F,x)
    end
end

or I can mimick the scheme used in functors.jl from Flux.jl but it is not very convenient