Suppose I have this function,

```
function foo(x)
if g(x)
h(x)
else
nothing
end
end
```

where x can be of many different types, and `h(x)`

is type-stable, in the sense that given `x`

’s type, `h(x)`

's type is correctly inferred by the compiler. How can I make `foo`

type-stable? I should be able to use `Nullable`

, but how can I refer to `h(x)`

's return type in the `else`

branch?

```
function foo(x)
if g(x)
Nullable(h(x))
else
Nullable{T}() # what should T be?
end
end
```

I suppose I could use a generated function with `Base.return_type`

, but that seems like overkill for such a common scenario.

See this discussion, especially the end. TL;DR: your best bet now is to rely on the type inference of the compiler (as you do above), but this may change in the future.

1 Like

Thank you for the reference, that was a very useful read.

I just tried this solution on 0.6, and it’s not (inferred as) type-stable. As for generated functions, calling `Base.return_types`

there triggers an error: `code reflection cannot be used from generated functions`

.

However, I found an alternative, type-stable solution with `broadcast`

:

```
nullify(b::Bool) = b ? Nullable{Bool}(true) : Nullable{Bool}()
function foo(x)
h.(nullify(g(x)), x) # the first argument to `h` is a dummy to trigger broadcasting to Null
end
# It works for arbitrary `g` and `h`, eg.:
g(x) = x < 10
h(_, x) = sin(x)
```

Broadcast’s handling of Nullable is done in `broadcast_c`

. It calls `Base._return_type(f, arg_types)`

, which gets evaluated at compile-time. If I replace `return_type`

with `Base._return_type`

in your example, `and_then`

becomes type-stable.

neat workaround, but possibly you should file the inference problem as an issue

It’s a little verbose, but instead consider

```
function and_then(f, x::Nullable)
if isnull(x)
S = Base.Broadcast._nullable_eltype(f, x)
if isleaftype(S)
Nullable{S}()
else
Nullable()
end
else
Nullable(f(unsafe_get(x)))
end
end
```

This is better for performance. Among possible inferred results `Union{Nullable{Int}, Nullable{Union{}}}`

we prefer `Nullable{Int}`

, so if inference can return a leaf type, then that’s what we return. Among possible inferred results `Union{Nullable{Int}, Nullable{Union{}}}`

and `Union{Nullable{Int}, Nullable{Integer}}`

, we prefer `Union{Nullable{Int}, Nullable{Union{}}}`

, because this prevents type instability from propagating very far (since `get`

can be inferred to return `Int`

on that).

In summary:

- Only use the result of type inference for the empty case
- Only use the result of type inference if it is a leaf type; otherwise use
`Union{}`

Note that this is the behavior also of `broadcast`

: we guarantee that we never return a `Nullable{T}`

where `T`

is abstract. This is very important for performance.

By the way, you should not use anything that calls inference from generated functions. This includes `broadcast`

and the implementations of `and_then`

in this thread. There is no workaround besides avoiding the use of generating functions.

(Of course, in this particular case, just use `broadcast`

)

2 Likes