Why Julia doesn't have function types?

I was wondering what’s the reason that in Julia we don’t have function types based on input variables, and output variables respective types. I mean, f(x::Int) = x would be of type Function{input<: Int} while f(x::Int)::Int = x would be Function{input<: Int, output<:Int}. Is there some “compiler related” reason or something like it?

1 Like

most functions in Julia wouldn’t have useful types… also, lots of functions would have undecidable types or types that can change at runtime.

2 Likes

So it’s more a matter of utility? I think that at leas the Function{input<:Type} would be quite common. Indeed, without the output type specified, I don’t think it’s that useful.

1 Like

What should the type of the function below be?

function foo(x)
  return 2*x
end

function foo(x::String)
  return 2*length(x)
end

Because the function is foo. There may be methods, e.g. foo(x::String). Even if that were the only method for the function [at the moment], later in execution, new methods may be defined.

6 Likes

You can always make a callable type-parametric sub-type of Function(an abstract type). However, there are infinite number of input and output combinations so that you will end up with many parameters to deal with in type annotations.

FunctionWrappers.jl kind of provides this, see e.g. the tests https://github.com/yuyichao/FunctionWrappers.jl/blob/2b76f6fb71ec67e8bb3a23a2158f2571955a59be/test/runtests.jl#L35

4 Likes

I mean, the first one would be type Function{Any, Any} the second would be Function{String, Any}. And you could do foo(x::String)::String = x*x which would be Function{String, String}, no?

1 Like

Thanks, @kristoffer.carlsson . Do you know if this package is still maintained? There is no documentation :confused:

BTW, I ended up using Catlab.jl. Here is my solution:

struct JFunction
    f::Function
    dom::Type
    codom::Type
end
(::JFunction)(x) = f(x)
@instance Category{Type, JFunction} begin
  dom(ȷf::JFunction) = getfield(ȷf,:dom)
  codom(ȷf::JFunction) = getfield(ȷf,:codom)

  id(d::Type) = JFunction(x->x, d, d)
  compose(ȷg::JFunction, ȷf::JFunction) = JFunction(ȷg ∘ ȷf, ȷf.dom, ȷg.codom)
end
1 Like

There is no “first one” and “second one”, the two are the same function, if you are describing function types, then the description should be valid for all method bodies of the function. And note that new code can extend the function adding new method bodies to it. If you are describing methods, then you will have situations in which you do not know which method is being called in a block of code before runtime.

4 Likes

Hmmm, I think I get your point. I was wondering about language’s such as Haskell that do have this function type definition. I mean, what makes it unfeasible in Julia? Is the fact that is dynamic or the multiple dispatch?
I’ll try understanding the code in FunctionWrappers.jl to see what they are doing, since it seems that the package implements this.

3 Likes

I guess they are doing something like this, which would allow you to dispatch on function types (fun exercise):

julia> struct MyFunc{Input,Output} end

julia> (f::MyFunc{Input,Output})(x::Input) where {Input,Output} = Output(x)

julia> const myfunc = MyFunc{Int,Float64}()
MyFunc{Int64, Float64}()

julia> myfunc(1)
1.0

julia> const myfunc2 = MyFunc{Int,Int}()
MyFunc{Int64, Int64}()

julia> myfunc2(1)
1

julia> g(f::MyFunc{Int,Float64}) = "This is Int -> Float64"
g (generic function with 1 method)

julia> g(f::MyFunc{Int,Int}) = "This is Int -> Int"
g (generic function with 2 methods)

julia> g(myfunc)
"This is Int -> Float64"

julia> g(myfunc2)
"This is Int -> Int"

But maybe this is a XY problem? I don’t see people trying to dispatch on function types very often here.

4 Likes

Is your question more of an academic one or do you see something major that you can’t do right now because functions are untyped?

I’ve found one of the best parts of Julia to be that once you really understand the type system you can actually stop worrying about types for many things. Duck typing is great.

2 Likes

More of academic interest I guess. The question came as I was studying Category Theory, and the lecturer used Haskell as an example. I then wondered if it could be done in Julia.

5 Likes

Are you talking about the full type signature?

Eg, for example, the haskell equivalent (I think, typing this on my phone) of goretkin’s code above is:

foo :: Num a => a -> a
foo x = 2 * x

foo :: [Char] -> Int
foo x = length x

of course what’s annoying is that the terms “functions” and 'methods" mean completely different things in different languages

1 Like

I think a lot of the answer is that there isn’t a strong technical reason, but there is a very good social reason. Haskell is a really cool language, but it has an incredibly complicated type system that pretty much requires a basic understanding of category theory (or some group theory) to use effectively.
Julia’s design is based on a lot of the same category theory as Haskell, but exposing that complexity to the user directly makes it a lot harder to use for people who don’t have experience with abstract math.

4 Likes

I see. Indeed, perhaps it’s not that useful.
On a side note, it seems that FunctionWrappers.jl does not enforce type consistency of the domain/codomain. But the solution with Catlab.jl does :slight_smile: , which is quite nice. I mean, I can do:

f = FunctionWrapper{Int,Int}((x) -> x)
g = FunctionWrapper{Float64, Float64}((x) -> x)
f∘g

But in Catlab, I’d get an error right away.

1 Like

I think more important (at least in “everyday” haskell) is the link between haskell (or any typed functional language) and the simply typed lambda calculus: Simply typed lambda calculus - Wikipedia

This does have a link to (cartesian closed) categories.

Julia is not based on the lambda calculus (afaik), nor do I think it makes sense for a high performance lang of it’s type.

Catlab is very impressive, but as far as I can tell, it’s a different interpretation of categories in the context of computing (perhaps even more suited for numerical work, haskell w/ the standard prelude is pretty bad for that).

1 Like

FunctionWrappers.jl is needed if you want to store a function as a data field in a struct without causing type instability. This is the equivalent of function pointers in C, not necessarily some exotic Haskell-like stuff. It would be useful, e.g., if you want to set up a virtual function table manually to emulate single dispatch of classic OOP.

5 Likes

Because in Haskell you can’t define multiple methods for the same function except with parametric polymorphism or type classes. The former is equivalent to a single method definition in Julia with a where clause, and the latter explicitly constrains all of the methods (“instances”) to have the same form of type signature.

8 Likes

exactly (or C++), the conclusion being that having typed function pointers doesn’t make a language more haskell, the typing system doesn’t work with these informations (i.e. you can’t specify a function to take only “functions that takes A and spits out B”