Can I get the declared return type of a method?

f(a :: Int8, b :: Float32) :: Float64 = a + b

I have found ways to get the types of arguments of a method, such as (first(methods(f))).sig. But I don’t see a way to get the return type I declared. Is there / could there be a way?

Given that this value isn’t guaranteed to exist, it’s hard to imagine a place where this is what you want to do.

While you are correct, I do think that most methods are called with the expectation that they will return someday. Also, would not be hard to have a special type to be declared as return type of methods that never return (or just default to nothing).

What I meant is that for example the function x()=rand()>.5? 1: 1.0 doesn’t have 1 return type. It either returns an Int or Float64, but you can’t tell from the method signature. (also Union{Int, Float64} would be incorrect since that is not a concrete type so no variable can have that type).

It is also impossible to determine if a given method ever returns.

1 Like

Bob posted thus question on my behalf.

I’m writing a program that uses constraint propagation to solve a puzzle. The program cycles through a set of “rule functions” until the puzzle is solved or no progress is made. All rule functions have the same argument signature and the same return type, and no other functions in my program will have the same argument and return signatures.

Rather than having to add each rule function to a collection when it’s defined, I thought I could use Julia’s introspection capabilities to discover them. I prefer this approach since Julia is annoyingly sensitive to definition order.

I understand that in many cases the return type of a method might be ambiguous. I am only concerned about the method’s declared return type, which Julia presumably stores someplace.

Thanks.

Oh, ok. But (first(methods(f))).sig may not return an abstract type too? As you can define some parameter as Union, Any, or Abstract...? Seems that in the context of this question an abstract type would be fine too.

I carefully posed the problem as finding the declared type, if any. I don’t expect to get an inferred type.

Julia would be much happier giving you the inferred type ;). The declared type isn’t a real thing. It’s a syntactic aid that lowers return x to return convert(T, x). In particular, it doesn’t even need to be a real type:

julia> foo(x, y)::typeof(x + y) = 1
foo (generic function with 1 method)

julia> foo(1, 1)
1

julia> foo(1, 1.0)
1.0
11 Likes

Regardless, where the return type is simple as in your example, asking for the inferred type and the declared type will be the same thing (unless the function is known to error), so maybe just try that.

I’m curious what this is in reference to. Julia’s method semantics are carefully designed so that definition order does not matter.

5 Likes

The language implementation has other definition order issues, for example using a type in a declaration before it is defined. I understand done cases if thus have been resolved, but I still experience such problems in the newest release.

Also, if I were to define a global collection of my constraint functions, I expect it’s definition could not appear before all of those functions are defined.

Keno, you didn’t say, how would I enquire of Julia to happily provide me the inferred return type?

1 Like
julia> Core.Compiler.return_type(x -> sin(x), (Int, ))
Float64
5 Likes

Thanks.

I highly doubt this is the only / best approach to a constraint solver though. Maybe you should try something else.

1 Like

Unfortunately, Core.Compiler.returb_tyoe isn’t helpful. It I just returning Union{}, not sufficiently distinctive for my needs.

Also, though maybe I should open a new topic for this:

The signature of a method as appears in it’s sig field, is a Tuple type, e.g. Tuple{typeof(function name),Int32, Float64}. How can I determine the number of component types and get each one?

If all the functions have the same input and output types, you may be looking for FunctionWrappers.jl.

How many different functions do you have? For example, can you just make a tuple of them as your global container once they are defined? It’s not clear what you mean by “discovering” them.

1 Like

Or even define them in the tuple:

julia> const rules = (
           function rule1(x, y)::Int
             x + y
           end,
           function rule2(x, y)::Int
             x*y
           end
       );

julia> for rule in rules
         println(rule(1,2))
       end
3
2
1 Like

Maybe one solution to your problem would be to use functors instead of functions. Then you can use introspection to find all subtypes of an abstract Rule type (using subtypes).

E.g.


abstract type Rule; end

struct Rule1 <: Rule; end
(r::Rule1)(x, y) = x + y

struct Rule2 <: Rule; end
(r::Rule2)(x,y) = x*y

x,y = 1,2
for R in subtypes(Rule)
   fn = R()
   println(fn(x,y))
end
4 Likes

Thanks haberdashPI. I think that’s the cleanest, most useful suggestion I’ve had yet. I also like that I’d be reusing the same singletons.

In answer to pfitzseb, having a collection of my constraint functions is what I’m currently doing, but it is unsatisfactory because I expect it would impose definition order issues. Each constraint definition might be preferred by supporting functions and followed by a block of test code.