# Dependent Function Types - More Details

Initial implementation in PR #3464.

## Syntax

``````FunArgTypes       ::=  InfixType
|  ‘(’ [ FunArgType {',' FunArgType } ] ‘)’
|  ‘(’ TypedFunParam {',' TypedFunParam } ‘)’
TypedFunParam     ::=  id ‘:’ Type
``````

Dependent function types associate to the right, e.g. `(s: S) => (t: T) => U` is the same as `(s: S) => ((t: T) => U)`.

## Implementation

Dependent function types are shorthands for class types that define `apply` methods with a dependent result type. Dependent function types desugar to refinement types of `scala.FunctionN`. A dependent function type `(x1: K1, ..., xN: KN) => R` of arity `N` translates to:

``````FunctionN[K1, ..., Kn, R']:
def apply(x1: K1, ..., xN: KN): R
``````

where the result type parameter `R'` is the least upper approximation of the precise result type `R` without any reference to value parameters `x1, ..., xN`.

The syntax and semantics of anonymous dependent functions is identical to the one of regular functions. Eta expansion is naturally generalized to produce dependent function types for methods with dependent result types.

Dependent functions can be implicit, and generalize to arity `N > 22` in the same way that other functions do, see the corresponding documentation.

## Examples

The example below defines a trait `C` and the two dependent function types `DF` and `IDF` and prints the results of the respective function applications:

``````trait C { type M; val m: M }

type DF = (x: C) => x.M

type IDF = (x: C) ?=> x.M

@main def test =
val c = new C { type M = Int; val m = 3 }

val depfun: DF = (x: C) => x.m
val t = depfun(c)
println(s"t=\$t")   // prints "t=3"

val idepfun: IDF = summon[C].m
val u = idepfun(using c)
println(s"u=\$u")   // prints "u=3"
``````

In the following example the depend type `f.Eff` refers to the effect type `CanThrow`:

``````trait Effect

// Type X => Y
abstract class Fun[-X, +Y]:
type Eff <: Effect
def apply(x: X): Eff ?=> Y

class CanThrow extends Effect
class CanIO extends Effect

given ct: CanThrow = new CanThrow
given ci: CanIO = new CanIO

class I2S extends Fun[Int, String]:
type Eff = CanThrow
def apply(x: Int) = x.toString

class S2I extends Fun[String, Int]:
type Eff = CanIO
def apply(x: String) = x.length

// def map(f: A => B)(xs: List[A]): List[B]
def map[A, B](f: Fun[A, B])(xs: List[A]): f.Eff ?=> List[B] =
xs.map(f.apply)

// def mapFn[A, B]: (A => B) -> List[A] -> List[B]
def mapFn[A, B]: (f: Fun[A, B]) => List[A] => f.Eff ?=> List[B] =
f => xs => map(f)(xs)

// def compose(f: A => B)(g: B => C)(x: A): C
def compose[A, B, C](f: Fun[A, B])(g: Fun[B, C])(x: A):
f.Eff ?=> g.Eff ?=> C =
g(f(x))

// def composeFn: (A => B) -> (B => C) -> A -> C
def composeFn[A, B, C]:
(f: Fun[A, B]) => (g: Fun[B, C]) => A => f.Eff ?=> g.Eff ?=> C =
f => g => x => compose(f)(g)(x)

@main def test =
val i2s = new I2S
val s2i = new S2I

assert(mapFn(i2s)(List(1, 2, 3)).mkString == "123")
assert(composeFn(i2s)(s2i)(22) == 2)
``````

### Type Checking

After desugaring no additional typing rules are required for dependent function types.