# Narrowing a function

[Narrowing](../narrowing.md) › function. The names in the examples are those of [section 2](../narrowing.md#2-the-names-in-the-examples).

A function type is `{:function, parameters, result}`: `function<number> -> number` is a function of one number that gives a number or `null`. Parameter names are not part of the type, and a function has **no value set**. A fact about a function can say only whether it is `null`, and which function types it is an instance of: a function is the function types it was declared with, and `f instance of T` asks whether one of them is within `T` (`Feel.Type.Arrows`, D-171). The internal `:function` kind holds every function regardless of its parameters. It appears in remainders of `Any` and as a fallback when erasing an arrow would strengthen its domain; it has no declared FEEL syntax. A function type minus another is a type (D-152), and is empty when a function type of the function is within the other; a built-in function with several signatures is all of them, an overload ([overloaded functions](overloaded-functions.md#6-a-built-in-as-a-value)).

A function is written `function<A> -> R | Null` below, the last `| Null` being the function's own: a name of a function type can be `null` like any other.

Recorded arrows guarantee signatures without excluding others, including signatures with the same arity and different parameter domains. A positional call outside every recorded domain starts at `Any | Null`, then applicable call refinements narrow it. A declaration alone cannot prove that the call fails. Known literal functions carry their syntax separately. `Feel.Recursion` similarly prepares linked members with internal single-signature evidence for their peers, keeping result bounds for recursive calls with an argument of `Any`. This evidence does not apply to ordinary host bindings or a local parameter that shadows a peer's name. It supplements closure discovery: literal bodies reached through contexts, list indexes, conditionals or iterators still specialize for the actual arguments. Complete peers also bound named calls by their written result projection or `Null` when the argument count permits binding, without claiming that the names match. Unknown and duplicate names stay runtime errors; excessive named arguments give only `Null`. The marker survives context-entry binding when a container exceeds the 32-entry shape limit. When body specialization reaches its depth or source budget, the analyzer uses the marked complete written signature even if closure discovery still succeeds. Discovery keeps its precedence while specialization is available. Descendant selection carries completeness from its source, including the functions inside the resulting lists. A source containing partial host functions or unknown values does not establish that evidence merely because the selected values have function types. Those calls keep conservative result bounds and checks.

Function literals distinguish their observable arrow from what their body returns on a call. For `function(x: number) 1`, the runtime result signature is nullable `number`, while the call refinement returns exactly `1`. `instance of` reads the arrow, not that refinement. Lists and contexts preserve literal closures' call refinements; normalization does not widen their parameter domains. The signature comes from kind-only analysis of the written environment, before condition facts or call specialization; full analysis performs that pass before refining the body (D-219). Optimization preserves the captured signature too. For example, when `v` is declared `Any`, a `function() v` written under `if v instance of number` still has an observable `Any` result, even though a call in that branch can be known to return a number. This keeps `instance of`, callback acceptance and required result checks consistent across tiers and backends. Public projection keeps outer non-null parameters and falls back to `Any` if a safe domain cannot be written in the public type language. See [the signature contract](../type-system.md#function-signatures).

For [guarded recursive data results](../type-system.md#guarded-recursive-types), an enforced result declaration stays the exact runtime signature on both backends. Its observable analysis result is `Any | Null`; the structural projection is a call refinement, not proof of a structural signature. This also applies to functions declared in scopes and supplied as values. Recursive parameter domains use an empty lower bound in upper arrows. Functions inside recursive graph projections, overlapping runtime signatures and complete peers may keep broader call bounds. These approximations must keep reachable else branches and uncertain runtime checks.

Specialization also sees recursive parameters after conversion. It discards argument shape, callee and origin evidence that singleton conversion may invalidate, including in returned closures.

| Expression | Type | Warnings |
| --- | --- | --- |
| `function(a: number) 1` | `overload<function<number \| Null> -> number \| Null with function<number \| Null> -> number = 1>` | — |
| `(function(a: number) 1)(2)` | `number = 1` | — |
| `(function(a: number) 1) instance of function<number> -> number!` | `boolean` | — |

## `null`, and `instance of`

| Condition | `f` when true | `f` when not true |
| --- | --- | --- |
| `f = null` | `Null` | `function<number \| Null> -> number \| Null` |
| `f != null` | `function<number \| Null> -> number \| Null` | `Null` |
| `f instance of function<number> -> number` | `function<number \| Null> -> number \| Null` | `Null` |
| `f instance of function<string> -> number` | `overload<function<number \| Null> -> number \| Null with function<string \| Null> -> number \| Null>` | `function<number \| Null> -> number \| Null except function<string \| Null> -> number \| Null \| Null` |
| `f instance of function<Any> -> number` | `function<Any \| Null> -> number \| Null` | `function<number \| Null> -> number \| Null except function<Any \| Null> -> number \| Null \| Null` |

The last column of the last two rows is the function type followed by `| Null`: the alternative `Null` stays, and so does the function. The `then` of the second row is an overload: the function of a number, which behaves as the test says (gives a number or `null`) for a string.

- **`f instance of T` removes `null`** from `then` for a function target `T`, since `null` is an instance of `Null` only.
- **The meeting of two function types** (`Feel.Type.Union.intersect/2`, `Feel.Type.Arrows.meet/2`, D-171) is the one that is within the other (`function<Any> -> number` is within `function<number> -> number`: a function of anything is a function of a number, so `then` has the test's type), and if neither is, the function's own type together with the test's type, which describes what a call does for the arguments it takes. For the narrowing, and for a reader that does not call it, the result is the function's own type, which holds every function the name can be; a call finds the other ([overloaded functions](overloaded-functions.md#7-function-values-with-separate-types)).
- **In `else`** the function is removed if its type is wholly within `T` (`Union.subtype?/2`), as in `f instance of function<number> -> number`; if it is not, what is left is the function type less `T` (`function<...> -> ... except function<...> -> ...`, D-152), a type that holds the functions of the name that are not instances of `T`.
- **Parameters are contravariant and the result covariant** in that test, as the conformance rules of the lattice have it ([type-system.md](../type-system.md), 2): a `function<Any> -> number` is wherever a `function<number> -> number` is expected.

## What a function does not narrow

| Expression | Type | Warnings |
| --- | --- | --- |
| `f(x)` | `number \| Null` | — |
| `if f != null then f(1) else 0` | `number \| Null` | — |
| `f(x, y)` | `Any \| Null` | — |

Removing `null` from the callee does not remove `null` from its declared result: `f(1)` stays `number | Null` after `f != null`. An `instance of` fact can additionally refine what a call returns for arguments in the tested domain; it does not establish a function's implementation. A call with a different number of arguments is `Any | Null`: the type says that the function has this arrow, not that it has no other (`ceiling` is a `function<number> -> number` and takes a scale too, D-171); a function value known from its syntax or registry can establish a real arity error, but an overload type alone cannot. A named call through a type alone is `Any | Null`, since names can select an unrecorded signature. How a call of a value is typed, and the choice between several function types, are covered in [overloaded functions](overloaded-functions.md).

## `Any` and functions

A name of type `Any` is taken kind by kind by a comparison, and the function is the one kind that has no value set: where a function may be accepted, the `then` has `function`, a function of any parameters (`:function`, D-39, D-152):

| Condition | `v` when true | `v` when not true |
| --- | --- | --- |
| `v instance of function<number> -> number` | `function<number \| Null> -> number \| Null` | `Any except function<number \| Null> -> number \| Null \| Null` |
| `v = f` | `function \| Null` | `Any \| Null` |

`v = f` can be true when `v` is a function (two functions are equal when they are the same value), and the narrowing does not say which, so `v` is `function`; and `null` is included when `f` may be `null`. An ordering never accepts a function, so `v > 5` leaves the numbers ([Any](any.md)). `instance of` is the one condition that takes functions out of `Any` in `else`: `v instance of function<number> -> number` leaves `Any`, since a function of other parameters may be there, and the other conditions of [Any](any.md#instance-of) take the other kinds out and leave the function in. The allowed values of a name of type `Any` are read in the same way: `= f` leaves `function | Null`.

## Limits

- No finite set of function values. Exclusions operate on signature descriptions (D-171), subject to bounded representation and projection. The signature/projection contracts are [stated in the assessment and kept as regressions](../type-system-analysis.md#21-function-signatures-and-call-refinements). These bounds do not provide a complete semantic decision procedure or establish equal behavior for two functions with matching metadata.
- A call by name does not look at the names of the parameters: the type has none, and it cannot be known which ones the call supplies.
- A function that is written in the expression and called there is analyzed again with the types of its arguments, so the facts that hold at the call narrow its body ([where facts apply](../narrowing.md#6-where-facts-apply), D-193). The same holds for one that the callee node identifies: a key of a context literal, an item of a list literal, a branch of an `if` (D-193, [overloaded functions](overloaded-functions.md#a-callee-that-says-which-function-it-is-d-193)), and the call has the values that are also in what the type of the callee gives, which a condition narrows (`f instance of function<number> -> number` before `f(1)`). The same holds for one that a name holds in a list or a context that is written out, one that a parameter is given as an `if`, an item or a key, and the iterator of a `for` over a list of them (D-193): the shapes of the structure of a name keep which functions its items and entries are. The same holds for a function that a call returns (`g(1)(2)`, D-193), one that is read through a list or a context literal that is selected from more than once (`{fs: [abs, floor]}.fs[1](x)`), and one that a parameter is given in a list or a context.
- A function that reaches the call through a call that returns a list or a context, through a call that may give a value that is not a function, or in more than 4 alternatives is typed by the type: the type of a function says its arrows and nothing of the body.

See also: [overloaded functions](overloaded-functions.md), [Any](any.md), [null](null.md).
