# Function types

Why don’t the two functions:

``````a = t -> cos
b = t -> 0.
``````

have the same type? As a result, one cannot do the following:

``````fct = [a, a]
fct[1] = b
``````

since the list is of the same type of `a`. On the other hand,

``````fct = Any[a,a]
fct[1] = b
``````

works fine. The result is this could lead function instability if one is not careful. An `AbstractFunction` type might have been useful (not sure), but the `supertype` of `Function` is `Any`.

Thanks.

In a way, `Function` can be understood to actually be `AbstractFunction`.

``````julia> a = t -> cos
#5 (generic function with 1 method)

julia> b = t -> 0.
#7 (generic function with 1 method)

julia> fct = Function[a,a]
2-element Vector{Function}:
#5 (generic function with 1 method)
#5 (generic function with 1 method)

julia> fct[1] = b
#7 (generic function with 1 method)
``````
4 Likes

This is something FunctionWrapper.jl handles, though unfortunately it’s README is somewhat unhelpful. The test suite shows how to use it though.

``````using FunctionWrappers
import FunctionWrappers: FunctionWrapper

a = t -> cos(t);
b = t -> 0.

fct = FunctionWrapper{Float64, Tuple{Float64}}[a, a]
``````
``````julia> fct[1] = b;

julia> fct[1](1)
0.0

julia> fct[2](1)
0.5403023058681398
``````
6 Likes

The return of `cos` is probably a typo but if it isn’t, the simple answer is that two functions that must return different types should not have the same concrete type, though they both subtype the abstract type `Function`.

Now if the intent was `cos(t)::Float64`. For one, a generic function alone cannot determine the return type because it can have multiple methods each with multiple compiled specializations. At the very least, you need the function and concrete input types, in short the function call. But even then, a function call can be type-unstable. Julia’s functions are simply too flexible to lump different ones under the same concrete type. A sort-of exception is anonymous functions made in an array comprehension, but those can share a type because they’re really using one underlying function.

FunctionWrappers.jl is a way for you to use the type system to promise that a function will be called with the specified input types and return type. In the example above, you might have noticed that although the wrapper promised a `Float64` input, an `Int` input was accepted. That’s because there are actually implicit type conversions for inputs and output. For example, `fct[1](1im)` would throw an `Inexacterror: Float64(0 + 1im)`. So, you do have to be careful to stick with convertible types.

1 Like

@erlebach in Julia the function type has nothing to do with a signature (definition of inputs and outputs). A function is like an identifier for a set of methods. Each method has a signature, but not the function itself.

As I understand, writing `foo(a) = 2*a` does basically three things:

1. Define a new type to represent the function `foo`.
2. Define a new method (taking one argument of type `Any`) for this function.
3. Define a constant global `foo` holding a value of this new type (see below).

And later writing `foo(a,b) = a+b` simply adds a new method (taking two arguments of type `Any`) to the same function `foo`.

Writing `b = t -> 0` also defines a type for the function and adds a method to it. The difference is that in this case the function has no given name (though internally a hidden name is automatically generated), and no global is defined to hold its value. But the user can still store the value in a variable of their choice, as we do here with variable `b`.

So the situation is a bit the opposite of what you apparently expected: in Julia if two “functions” have the same signatures (but we’re really talking of methods here) they cannot have the same function type! On the other hand you can have many methods with different signatures that have the same function type…

You can always find the type of a function with `typeof`, for example `typeof(foo)`.

In most cases (like the above) the function is a singleton: the type has only one possible value which is empty (0 bytes). For example `foo` holds the only value of type `typeof(foo)`, and `b` holds the only value of type `typeof(b)`.

Some functions can have several values, for example a closure:

``````add_x(x) = y -> y+x  # makes a closure to hold the value of x

(3, 4)

true

#3 (function of type var"#3#4"{Int64})
x: Int64 2

#3 (function of type var"#3#4"{Int64})
x: Int64 3
``````

Here `var"#3#4"` is the “hidden” name generated automatically for the anonymous function type (and `#3` is the name of the function of that type).

The closure example also shows that a function type can be parametric. In this case the same function can have several concrete types:

``````julia> typeof(add2)
var"#3#4"{Int64}

var"#3#4"{Float32}

false
``````

Note however that the type parameter concerns only the value stored in the closure. The different concrete types share the same method definitions:

``````julia> methods(add2f) == methods(add2)
true
``````

Another case of function type with several values is a callable struct. For example this is the definition of `Splat` in Base:

``````splat(f) = Splat(f)

struct Splat{F} <: Function
f::F
Splat(f) = new{Core.Typeof(f)}(f)
end
(s::Splat)(args) = s.f(args...)
``````

Note that declaring the struct as subtype of `Function` is optional. In Julia anything can be a function: every value has a type and every type can have methods. For example, we can define a method on strings:

``````(s::String)(a) = "\$a says \$s"
``````

and now every string is callable:

``````julia> methods("hello")
# 1 method for callable object:
[1] (s::String)(a)
@ Main REPL[1]:1

julia> "hello"(3)
"3 says hello"
``````

Where it gets quite confusing I think is with constructors: as I understand, `String([0x61, 0x62])` is not actually calling a method of the “String function” (unlike `"hello"(3)` above). Since `typeof(String) == DataType`, it’s actually calling a method of the “DataType function”.

The above is my summary of the documentation here and here (note that `TypeName` on that page refers to a struct, not the type name itself).

3 Likes