È possibile utilizzare il plug-in di continuazioni. Dopo che il plugin fa funzionare la sua traduzione, ha delle somiglianze con la monade Cont
e la shift
e la reset
di Oleg. La parte difficile è capire i tipi. Così qui è la mia traduzione:
import util.continuations._
import collection.mutable.ListBuffer
sealed trait Zipper[A] { def zipUp: Seq[A] }
case class ZDone[A](val zipUp: Seq[A]) extends Zipper[A]
case class Z[A](current: A, forward: Option[A] => Zipper[A]) extends Zipper[A] {
def zipUp = forward(None).zipUp
}
object Zipper {
def apply[A](seq: Seq[A]): Zipper[A] = reset[Zipper[A], Zipper[A]] {
val coll = ListBuffer[A]()
val iter = seq.iterator
while (iter.hasNext) {
val a = iter.next()
coll += shift { (k: A=>Zipper[A]) =>
Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
}
}
ZDone(coll.toList)
}
}
Il plugin continuazioni ha il supporto per while
ciclo, ma non per map
o flatMap
, così ho fatto la scelta di utilizzare while
e mutabile ListBuffer
per catturare gli elementi eventualmente aggiornati. La funzione make_zipper
viene convertita nel compagno Zipper.apply
- una tipica posizione Scala per la creazione di nuovi oggetti o raccolte. Il tipo di dati è tradotto in un tratto sigillato con due classi di casi estesi. E ho messo la funzione zip_up come un metodo di Zipper
con diverse implementazioni per ogni classe di casi. Anche piuttosto tipico.
Qui è in azione:
object ZipperTest extends App {
val sample = for (v <- 1 to 5) yield (v, (1 to v).reduceLeft(_ * _))
println(sample) // Vector((1,1), (2,2), (3,6), (4,24), (5,120))
def extract134[A](seq: Seq[A]) = {
val Z(a1, k1) = Zipper(seq)
val Z(a2, k2) = k1(None)
val Z(a3, k3) = k2(None)
val Z(a4, k4) = k3(None)
List(a1, a3, a4)
}
println(extract134(sample)) // List((1,1), (3,6), (4,24))
val updated34 = {
val Z(a1, k1) = Zipper(sample)
val Z(a2, k2) = k1(None)
val Z(a3, k3) = k2(None)
val Z(a4, k4) = k3(Some(42 -> 42))
val z = k4(Some(88 -> 22))
z.zipUp
}
println(updated34) // List((1,1), (2,2), (42,42), (88,22), (5,120))
}
Come ho fatto a capire i tipi per shift
, k
e reset
o il modo di tradurre T.mapM
?
Ho guardato mapM
e so che mi consentirà di ottenere un Cont
, ma non sono sicuro di cosa si trovi all'interno dello Cont
in quanto dipende dal turno. Quindi inizio nel turno. Ignorando l'haskell return
per creare un Cont
, lo spostamento restituisce un Zipper
. Immagino anche di dover aggiungere un elemento di tipo A
alla mia raccolta da compilare. Quindi lo spostamento sarà nel "buco" in cui è previsto un elemento di tipo A
, pertanto k
sarà una funzione A=>?
. Assumiamo questo. Metto dei punti interrogativi dopo i tipi di cui non ero tanto sicuro. Così ho iniziato con:
shift { (k: A?=>?) =>
Z(a, ?)
}
Poi ho guardato (duro) a (k . maybe a id)
. La funzione maybe a id
restituirà un A
, in modo coerente con ciò che assume k
come argomento. È l'equivalente di a1.getOrElse(a)
. Anche perché ho bisogno di compilare Z(a, ?)
, ho bisogno di capire come ottenere una funzione da opzione a Zipper. Il modo più semplice è supporre che k
restituisca uno Zipper
.Inoltre, osservando come viene usata la cerniera k1(None)
o k1(Some(a))
, so che devo dare un modo per l'utente di sostituire facoltativamente gli elementi, che è ciò che fa la funzione forward
. È continua con l'originale a
o un a
aggiornato. Inizia a dare un senso. Così ora ho:
shift { (k: A=>Zipper[A]) =>
Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
}
Successivo guardo mapM
di nuovo. Vedo che è composto con return . ZDone
. Ignorando nuovamente return
(perché è solo per la monade Cont
), vedo che ZDone
prenderà la raccolta risultante. Quindi è perfetto, ho solo bisogno di inserire coll
al suo interno e nel momento in cui il programma arriva lì, avrà gli elementi aggiornati. Inoltre, il tipo dell'espressione all'interno di reset
è ora coerente con il tipo restituito di k
che corrisponde a Zipper[A]
.
Infine, inserisco il tipo di reset che il compilatore può dedurre, ma quando indovino giusto, mi dà un (falso) senso di sicurezza, so cosa sta succedendo.
La mia traduzione non è generale o carina come quella di Haskell perché non preserva il tipo dalla raccolta e utilizza la mutazione, ma si spera che sia più facile da capire.
Edit: Qui è la versione che conserva il tipo e utilizza una lista immutabile in modo che z.zipUp == z.zipUp
:
import util.continuations._
import collection.generic.CanBuild
import collection.SeqLike
sealed trait Zipper[A, Repr] { def zipUp: Repr }
case class ZDone[A, Repr](val zipUp: Repr) extends Zipper[A, Repr]
case class Z[A, Repr](current: A,
forward: Option[A] => Zipper[A, Repr]) extends Zipper[A, Repr] {
def zipUp = forward(None).zipUp
}
object Zipper {
def apply[A, Repr](seq: SeqLike[A, Repr])
(implicit cb: CanBuild[A, Repr]): Zipper[A, Repr] = {
type ZAR = Zipper[A, Repr]
def traverse[B](s: Seq[A])(f: A => [email protected][ZAR]): List[B]@cps[ZAR] =
if (s.isEmpty) List()
else f(s.head) :: traverse(s.tail)(f)
reset {
val list = traverse(seq.toSeq)(a => shift { (k: A=>ZAR) =>
Z(a, (a1: Option[A]) => k(a1.getOrElse(a)))
})
val builder = cb() ++= list
ZDone(builder.result): ZAR
}
}
}
A proposito, qui ci sono risorse aggiuntive sulla monade continuazione nella scala:
- http://blog.tmorris.net/continuation-monad-in-scala/
- cosa Mi sono messo in contatto quando ho provato per la prima volta: https://gist.github.com/huynhjl/4077185; include la traduzione in Scala di vari esempi di continuazione di Haskell e alcuni esempi tratti dal documento di Tiark (ma non l'uso del plugin di continuazione che indica più chiaramente la somiglianza tra l'approccio).
- se si scarica scalaz, si può essere in grado di creare un'istanza Monad scalaz di Tony Morris e utilizzare lo scalaz traverse - quindi la traduzione su scala sarebbe quindi più letterale.
Grazie per questa risposta molto dettagliata! (BTW: Se vuoi modificarlo, penso che intendessi "seq" invece di "sample" nella seconda riga di extract134). Cosa intendi con "non preserva il tipo dalla raccolta"? Non vedo "Qualsiasi" visualizzato nel codice. –
Inoltre, hai detto che le continuazioni non supportano la mappa, ma è quello a cui mi riferivo con quel link alle diapositive di Rompf (vedi la diapositiva 48 per la definizione di mappa che supporta le continuazioni). Ho bisogno di farlo in uno stile puramente funzionale, quindi inizierò con quello che hai scritto e cercherò di modificarlo. Qualche suggerimento per quello? –
Sono stato in grado di capire come usare la mappa sospesa di Rompf per creare una versione puramente funzionale: http://pastie.org/7460281 Grazie per il tuo aiuto! –