How to type annotate functions which take functions as args


#1

Hi there,
Julia’s dispatch is something we all love, but I can’t figure out the syntax for the case of creating the signature of a function which takes another function as an arg.

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.0.0 (2018-08-08)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> function f(x::Number)
              return x^2+1
              end
f (generic function with 1 method)

julia> function g(x::Number)
              return sqrt(x-1)
              end
g (generic function with 1 method)

julia> f(2)
5

julia> g(5)
2.0

julia> function h(_f, x :: Number)
              return g(_f(x))
              end
h (generic function with 1 method)

julia> h(f, 1)
1.0

If I wanted to annotate the function h’s arg _f, how would I do so? I was hoping for something like Haskell where you could say a->a (NB, I’m not a haskell programmer but I’m acquainted with the concepts). I suspect I don’t fully understand Julia’s type system when it comes to functions because if I try…

julia> typeof(h)
typeof(h)

… I get output which to me seems unhelpful. While my code works without such an annotation, I hope making that explicit is supported syntax.

If you think you see the gap in my understanding and/or have resources explaining this part of the language I’d be very grateful!
Cheers


#2

Did you try to annotate _f with this? It actually works!

julia> function h(_f::typeof(f), x::Number)
           return g(_f(x))
       end
h (generic function with 1 method)

julia> h(f, 1)
1.0

julia> h(g, 1)
ERROR: MethodError: no method matching h(::typeof(g), ::Int64)
Closest candidates are:
  h(::typeof(f), ::Number) at REPL[26]:2
Stacktrace:
 [1] top-level scope at none:0

#3

Alternatively if you just want your function h to call any function passed, not just f (which is presumably the case since otherwise you wouldn’t have it as an argument and just fixed in the code) you can use the ::Function annotation. (I don’t know Haskell so I can’t tell if that’s what you are trying to do.) For example,

julia> function h(_f::Function, x::Number)
           return g(_f(x))
       end
h (generic function with 1 method)

julia> supertype(typeof(f))
Function

julia> f isa Function
true

julia> g isa Function
true

That said, it’s often useful not to restrict functions to subtypes of Function since there are other ways of creating objects that you can call. For example, you can have a callable struct.

struct MyStruct
    a::Float64
end

function (mystruct::MyStruct)(b::Number)
    mystruct.a + b
end

julia> astruct = MyStruct(1.2)
MyStruct(1.2)

julia> astruct(3)
4.2

julia> astruct isa Function
false

As such, I tend to leave off function annotations and just try calling whatever is passed. (Cf. duck typing.)


#4

I’m pretty sure in Haskell this means something like “a function with one argument, and one return value, which both have the same type”. Is that right? I don’t think you can do that in Julia, at least partially because functions can’t be described in that way. Consider

f(x)=1
f(x,y)=2

The function f cannot be described with a single number of arguments, because it has methods with many numbers of arguments.


#5

A couple of relevant prior discussions, in case you’re interested: