La soluzione semplice
La cosa migliore quando si tratta di tipi di percorso-dipendente è quello di mantenere sempre il valore proprietario in giro, perché Scala ha inferenza molto limitato e capacità di ragionamento altrimenti.
Ad esempio, l'esempio di codice potrebbe essere riscritto come:
Platform.parse("ios") flatMap {
p => p.parseArch("arm64").map(exec(p))
}
E 'generalmente possibile eseguire tali riscritture, anche se il codice viene spesso diventano meno conciso ed elegante. Una pratica comune è usare le funzioni dipendenti e le classi parametriche.
Utilizzando tipi dipendenti
Nel tuo esempio, il codice:
Platform.parse("ios").flatMap(p => p.parseArch("arm64").map(a => (p, a)))
è di tipo Option[(Platform, Platform#Arch)]
, perché l'inferenza di Scala non può mantenere il fatto che secondo elemento della tupla dipende dal primo elemento. (Si ottiene A$A2.this.Platform
perché si è dichiarato Platform
in un contesto interno.)
In altre parole, il tipo di Scala Tuple2
non dipende. Si potrebbe correggere tale da rendere la nostra propria classe:
case class DepPair(p: Platform)(a: p.Arch)
Tuttavia, Scala non supporta ancora le firme di classe dipendenti, e non si compila. Invece, abbiamo impostato per utilizzare un tratto:
trait Dep {
val plat: Platform
val arch: plat.Arch
}
Platform.parse("ios")
.flatMap { p => p.parseArch("arm64").map { a =>
new Dep { val plat: p.type = p; val arch: p.Arch = a }}}
.flatMap { dep => exec(dep.plat)(dep.arch) }
Nota le attribuzioni sul val plat
e val arch
, come senza di loro, Scala cercherà di dedurre un tipo raffinato che renderà tipo controllo sicuro.
Siamo di fatto al limite di ciò che è ragionevole fare in Scala (IMHO). Ad esempio, se avessimo parametrizzata trait Dep[P <: Platform]
, avremmo ottenuto in tutti i tipi di problemi.In particolare:
Error:(98, 15) type mismatch;
found : Platform => Option[Dep[p.type]] forSome { val p: Platform }
required: Platform => Option[B]
Scala deduce un tipo di funzione esistenziale, ma quello che vorremmo è in realtà per avere la quantificazione esistenziale all'interno il tipo di funzione. Dobbiamo guidare Scala per capire che, e si finisce con qualcosa di simile:
Platform.parse("ios").flatMap[Dep[p.type] forSome { val p: Platform }]{
case p => p.parseArch("arm64").map{case a: p.Arch =>
new Dep[p.type] { val plat: p.type = p; val arch = a }}}
.flatMap { dep => exec(dep.plat)(dep.arch) }
Ora ti permetterà di decidere da che parte è la migliore: il bastone con il proprietario val
intorno (soluzione semplice), o il rischio perdendo il senso di sanità mentale che ti era rimasto!
Ma parlando di perdere la sanità mentale e esistenziali, cerchiamo di indagare un po 'più ...
Uso esistenziali (fallito)
Il tipo problematica del risultato intermedio nel codice era Option[(Platform, Platform#Arch)]
. V'è in realtà un modo per esprimere al meglio, utilizzando un esistenziali, come in:
Option[(p.type, p.Arch) forSome {val p: Platform}]
Possiamo aiutare Scala specificando esplicitamente, in modo che il risultato intermedio ha il tipo di destinazione:
val tmp: Option[(p.type, p.Arch) forSome {val p: Platform}] =
Platform.parse("ios")
.flatMap { case p => p.parseArch("arm64").map { a => (p, a): (p.type, p.Arch) }}
Tuttavia, ora tocchiamo un'area molto sensibile del sistema di tipi di Scala, e spesso causerà problemi. In realtà, non ho trovato un modo per esprimere la seconda flatMap
...
Cercando tmp.flatMap { case (p, a) => exec(p)(a) }
dà la molto utile:
Error:(30, 30) type mismatch;
found : a.type (with underlying type p.Arch)
required: p.Arch
Un altro studio:
tmp.flatMap {
(tup: (p.type, p.Arch) forSome {val p: Platform}) => exec(tup._1)(tup._2)
}
Error:(32, 79) type mismatch;
found : tup._2.type (with underlying type p.Arch)
required: tup._1.Arch
A questo punto, penso che ogni individuo ragionevole si arrenderebbe e probabilmente starà lontano dalla programmazione di Scala per alcuni giorni ;-)
Intersecando. Suppongo che i tipi di path-dependent siano ancora piuttosto approssimativi attorno ai bordi di scala. Dopo aver letto la tua risposta, ho rinunciato a trasmetterli in tuple (la soluzione 'trait' è interessante ma un po 'troppo prolissa) e invece ho fatto un tratto interiore per mantenere il riferimento al tratto esterno come mostrato di seguito. Grazie per aver approvato i miei dubbi. –
Grande. Si noti che è possibile creare la propria piattaforma 'val'' a' def', in modo da non dover tenere un puntatore aggiuntivo al tratto esterno (Scala già memorizza i puntatori ai tratti esterni nei tratti interni). –
Non conoscevo la differenza tra il puntatore 'def' e' val'. Non sarebbe "def" ugualmente creare un puntatore a qualunque cosa faccia riferimento alla sua _closure_ (non è sicuro se questa è una cosa in scala)? Forse potresti condividere un link? –