Nel caso in cui siate in viaggio lungo e non abbiate il vostro informe sulle mani, ecco come potete farlo in puro Scala. Può essere utile per coloro che non hanno familiarità con senza forma e coloro che non lo usano per qualche motivo.
Prima di tutto, avremo bisogno di un modo per iterare sui tipi, cioè rappresentare numeri naturali nei tipi. È possibile utilizzare qualsiasi tipo nidificato o semplicemente definirne uno nuovo con alcuni alias per i numeri:
sealed trait Nat
trait Zero extends Nat
trait Succ[N <: Nat] extends Nat
// enough for examples:
type _0 = Zero
type _1 = Succ[_0]
type _2 = Succ[_1]
type _3 = Succ[_2]
type _4 = Succ[_3]
// etc...
Naturalmente, se si usano spesso tipi come _42
e _342923
, sarebbe più conveniente prendere una esistente Tipo Nat
con alcune macro-magia per la costruzione di quelle da valori, ma per i nostri esempi è sufficiente.
Ora, il tipo di funzione dipendente AdderType
'abbastanza semplice:
// first we define the type which take a Nat type argument
trait AdderType[N <: Nat] {
type Out
def apply(i: Int): Out
}
// then we inductively construct its values using implicits
case object AdderType {
// base case: N = _0
implicit def zero:
AdderType[_0] { type Out = Int } =
new AdderType[_0] {
type Out = Int
def apply(i: Int): Out = i
}
// induction step: N -> Succ[N]
implicit def succ[N <: Nat, NOut](
implicit prev: AdderType[N] { type Out = NOut }
): AdderType[Succ[N]] { type Out = Int => NOut } =
new AdderType[Succ[N]] {
type Out = Int => NOut
def apply(i: Int): Out = k => prev(i + k)
}
}
Ora, per costruire un'istanza di AdderType
e applicarlo, si scrive una funzione che prende un N <: Nat
come argomento tipo e implicitamente costruisce AdderType[N]
:
def adder[N <: Nat](initial: Int)(
implicit adderFunction: AdderType[N]
): adderFunction.Out = adderFunction(initial)
Questo è tutto:
scala> val add3Numbers = adder_[_3](0)
add3Numbers: Int => (Int => (Int => Int)) = <function1>
scala> add3Numbers(1)(2)(3)
res0: Int = 6
Si può vedere che la soluzione pura non è molto più grande o più complicata di quella che utilizza senza forma (sebbene quest'ultima ci fornisca i tipi pronti per l'uso Nat
e DepFn
).
Un po 'oltre: se (in qualche caso più generale) non si vuole usare adderFunction.Out
, che a volte porta a problemi, ho anche una soluzione senza di essa. In questo caso particolare non è migliore, ma lo mostrerò comunque.
Il punto chiave è quello di aggiungere un altro parametro tipo per il tipo out: adder[N <: Nat, NOut]
, ma d'altra parte non può passare N
come un tipo di adder
, perché avremo bisogno di scrivere NOut
, che vuole essere dedotto (in caso contrario, che cosa è il punto). Così possiamo passare un argomento valore aggiunto, che contribuirà a derivare N
Tipo:
def adder[N <: Nat, NOut](n: NatVal[N])(initial: Int)(
implicit adderFunction: AdderType[N] { type Out = NOut }
): NOut = adderFunction(initial)
per costruire NatVal[N]
non abbiamo bisogno di creare un'istanza di ogni tipo Nat
, possiamo usare un piccolo trucco:
// constructing "values" to derive its type arg
case class NatVal[N <: Nat]()
// just a convenience function
def nat[N <: Nat]: NatVal[N] = NatVal[N]()
Ora qui è come lo si utilizza:
scala> val add3Numbers = adder(nat[_3])(0)
add3Numbers: this.Out = <function1>
scala> add3Numbers(1)(2)(3)
res1: this.Out = 6
si può vedere che funziona, ma non ci mostra i tipi effettivi. Tuttavia, questo approccio può funzionare meglio nei casi in cui si hanno diversi impliciti che dipendono dai membri di tipo degli altri. Voglio dire
def foo[AOut]()(implicit
a: A { type Out = AOut},
b: B { type In = AOut }
) ...
invece di
def foo()(implicit
a: A,
b: B { type In = a.Out }
) ...
Poiché non è possibile reffer a a.Out
nella stessa lista degli argomenti.
È possibile trovare il codice completo nel mio repo su Github.