Get signature of function

Is there any code analysis tool that can infer the signature of

f(x) = [x]

without requiring a call site? eg something that can tell me f(x::T) returns Vector{T}?

Afaik Cthulhu and JET require a concrete arglist for answering any queries.

If I wanted to build one, is it possible?

Core.Compiler.return_type(f, Tuple{T})?

This isn’t quite it

julia> Tuple(first(methods(f)).sig.parameters[2:end])
(Any,)

julia> Core.Compiler.return_type(f, Tuple{first(methods(f)).sig.parameters[2:end]...})
Vector (alias for Array{_A, 1} where _A)

because I want to get basically f(::T)::Vector{T} where T but that’s not what I get.

what do you mean? isn’t this quite literally

Vector{T} where T

There is no indication that the T in the return type is the same as the T in the argument.

You really want that T printed in there ? :joy:

(Sorry didn’t see your next post - it does make sense to want that connected I guess - but not sure that generalises in multiple dispatch. If you have multiple methods, it may not be the same relationship)

I don’t mind how it prints, I just want a value representing the type signature.

For example in Haskell, that is represented as a function (for a generic type a) from values of type a to values of type [a] (“list of as”).

ghci> f x = [x]
ghci> :info f
f :: a -> [a] 	-- Defined at <interactive>:4:1

Julia is not Haskell.

You passed in Any, you get out Any. (T where T)

You need to pass in a more specific type.

It’s a reasonable question, but yeah, julia’s type system is not equipped to deal with this sort of thing. Type inference in julia is heavily focused on concrete types, because every single function call has fully unconstrained potential for ad-hoc polymorphism.

Yes, in the example you gave, you could reasonably expect that the compiler should be able to predict that for any T you put in, a Vector{T} should come out, but it quickly falls apart as soon as you look at anything more complicated.

In general, the output types of a specific function call are going to depend precisely on the input types.

Haskell can avoid this because all functions have either parametric polymorphism, or polymorphism via type-classes which are both much much more rigid and constrained than julia’s approach with totally free-form multiple dispatch, and even then the Haskell compiler will sometimes need you to provide a type signature when it can’t figure out a parametric signature for a function.

3 Likes

Julia’s dynamic typing and type inference make generic functions more flexible than those in statically typed languages where you declare the return type given the parameters first, and I think you can’t infer something like the latter in most cases. Here’s a simple example where the return type depends on a generic function call, rather than the 1-element array literal in the OP:

foo(x::T) where T = bar(x)[]

bar(::Int) = String
bar(::String) = Float64
bar(::Float64) = rand(Bool, Int8, Int16, Int32, Int64, Int128)

Best you can say is foo(::T) has a return type Vector{bar(x)}, which isn’t the static analysis we want because it 1) is not an expression with T, and 2) depends on a runtime value. The bar(::Float64) method introduces type-instability to foo(::Float64), so the compiler infers Vector. If we must infer by method signature instead of call signature, that’s the best we can do.