Skip to content

Commit d335458

Browse files
authored
Remove all uses of the term functor from the documentation (#59414)
There is some persistent confusion around the term functor. There is of course a well established mathematical definition, but some programming languages have taken to reuse the term for various sorts of callable objects. Julia should not be one of them, and we did (as mentioned below) actually reach this conclusion ten years ago. Yet the terminology has somewhat stubbornly persisted. This commit removes it. However, it is only fair that when one makes sweeping categorical proclamations on terminology, there should be some history and justification that can be linked to in the future, so I will attempt to do so in this commit message. ## History (Claude helped with the research in this section, if I missed something, please let me know, so I can go yell at Claude) The first use of the term functors in Julia was in 2014 5c91960, introducing special function objects for common generic functions to improve performance of reductions. This was before we were able to specialize. These were refactored a bit in 0c631f8 (using call overloading instead), and 6e715da (split out into a separate file functors.jl), before ultimately being deprecated and removed in 2016. However, it should be noted that this only ever referred to this very particular design pattern, which hasn't been in the code base since 2016. The use that is still in there stems from the documentation and was introduced in Jan 2015 in 11ab1b3. That said, while even this documentation called the callable objects themselves `functors`, julia looked a fair bit different at the time with call overloading using a generic function `Base.call(f, args...)` (i.e. there was a much stronger distinction between generic functions and ordinary objects). Moreover, this transformation is functorial in the mathematical sense. That no longer holds true as much in modern julia. In any case, by 6 months later (aac4eca) we had decided not to use the term `functor` for this and switched mostly to `function object` although additional references remained. However, as I hope this makes clear, this terminology was never wide-spread in julia itself. ## Argument I think the central use of the term `functor` in mathematics and (compatibly) in functional programming is dispositive of the debate, particularly given the strong functional programming influences in julia. However, suppose we didn't care about that. Even in that case `functor` would still be bad terminology in modern Julia. In C++, the distinction is clear. There are first class functions and then there are classes that implement `operator()` and they are very much not the same, so it makes sense to distinguish them (even if the precise choice of terminology is unforunate). In the original version in Julia where we had `Base.call`, this was arguably analogous, but in modern julia where there is just one big method table and no fundamental distinction between functions and other kinds of objects, we can no longer make a semantic distinction. I also think it's unclear what people exactly mean by `functor`, and I think for any of the possible meanings there is a better and more precise term we use more commonly. 1. For something that can be called, I think we generally just use function, but if you want to emphasize that it applies to non-Function types as well, `callable` is a fine term. 2. Specifically for structs that have `(::Foo)(a, b)` methods. However, `callable struct` is precise here. 3. Only for those structs 2 that are also non-singleton or contain mutable state. I think it's pretty rare to specifically need to refer to these and I think when people do, they often forget that closure are also non-singleton and can reference mutable state (through implicitly introduced boxes). If that's what you mean, I think you just need to be precise about it. 4. To emphasize the struct that contains such data itself (as opposed to the function called). Here, I think `function object` is the best terminology we have, specifically since it emphasizes the object. It's possible I've missed a meaning that people have in mind, but I think that's precisely the problem. So, let's remove this term (as we had already decided 10 years ago).
1 parent 50379b9 commit d335458

File tree

6 files changed

+14
-12
lines changed

6 files changed

+14
-12
lines changed

base/reflection.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,9 @@ function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f)); kwargs
260260
return code_typed_by_type(tt; kwargs...)
261261
end
262262

263-
# support 'functor'-like queries, such as `(::Foo)(::Int, ::Int)` via `code_typed((Foo, Int, Int))`
263+
# support queries with signatures rather than objects to better support
264+
# non-singleton function objects such as `(::Foo)(::Int, ::Int)`
265+
# via `code_typed((Foo, Int, Int))` or `code_typed(Tuple{Foo, Int, Int})`.
264266
function code_typed(@nospecialize(argtypes::Union{Tuple,Type{<:Tuple}}); kwargs...)
265267
tt = to_tuple_type(argtypes)
266268
return code_typed_by_type(tt; kwargs...)

doc/src/manual/methods.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ with keyword arguments processed after the matching method is identified.
915915
## Function-like objects
916916

917917
Methods are associated with types, so it is possible to make any arbitrary Julia object "callable"
918-
by adding methods to its type. (Such "callable" objects are sometimes called "functors.")
918+
by adding methods to its type.
919919

920920
For example, you can define a type that stores the coefficients of a polynomial, but behaves like
921921
a function evaluating the polynomial:

test/arrayops.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2658,9 +2658,9 @@ end
26582658
@inferred map(Int8, Int[0])
26592659

26602660
# make sure @inbounds isn't used too much
2661-
mutable struct OOB_Functor{T}; a::T; end
2662-
(f::OOB_Functor)(i::Int) = f.a[i]
2663-
let f = OOB_Functor([1,2])
2661+
mutable struct OOB_Callable{T}; a::T; end
2662+
(f::OOB_Callable)(i::Int) = f.a[i]
2663+
let f = OOB_Callable([1,2])
26642664
@test_throws BoundsError map(f, [1,2,3,4,5])
26652665
end
26662666

test/operators.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ end
179179
@test (x -> (x, 3), x -> (x, 2), x->(x,1))(0) === (((0, 1), 2), 3)
180180
@test (x -> (x, 4), x -> (x, 3), x->(x,2), x-> (x, 1))(0) === ((((0, 1), 2), 3), 4)
181181

182-
# test that user defined functors only need to overload the two arg version
182+
# test that user defined callable structs only need to overload the two arg version
183183
struct FreeMagma
184184
word
185185
end

test/reduce.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -532,13 +532,13 @@ let f(x) = x == 1 ? true : x == 2 ? false : 1
532532
@test_throws TypeError all(map(f,[1,3]))
533533
end
534534

535-
# any and all with functors
535+
# any and all with callable structs
536536

537-
struct SomeFunctor end
538-
(::SomeFunctor)(x) = true
537+
struct SomeCallable end
538+
(::SomeCallable)(x) = true
539539

540-
@test @inferred any(SomeFunctor(), 1:10)
541-
@test @inferred all(SomeFunctor(), 1:10)
540+
@test @inferred any(SomeCallable(), 1:10)
541+
@test @inferred all(SomeCallable(), 1:10)
542542

543543

544544
# in

test/reflection.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,7 @@ end
691691
@test Base.code_typed_by_type(Tuple{Type{<:Val}})[2][2] == Val
692692
@test Base.code_typed_by_type(Tuple{typeof(sin), Float64})[1][2] === Float64
693693

694-
# functor-like code_typed(...)
694+
# signature-based code_typed(...)
695695
@test Base.code_typed((Type{<:Val},))[2][2] == Val
696696
@test Base.code_typed((typeof(sin), Float64))[1][2] === Float64
697697

0 commit comments

Comments
 (0)