2012-01-03 7 views
13

Esiste un ragionevole motivo per cui Option non è Traversable?Perché l'opzione non è percorribile?

In Scala 2.9, Seq(Set(1,3,2),Seq(4),Option(5)).flatten non viene compilato e deve essere semplicemente implementato le giunzioni di tratto Traversable per me. Se non è il caso, ci deve essere qualcosa che non vedo che non lo permetta. Che cos'è?

PS: Durante il tentativo di capire, ho raggiunto le cose terribili che compilano, come:

scala> Seq(Set(1,3,2),Seq(4),Map("one"->1, 2->"two")).flatten 
res1: Seq[Any] = List(1, 3, 2, 4, (one,1), (2,two)) 

PS2: so di poter scrivere: Seq(Set(1,3,2),Seq(4),Option(5).toSeq).flatten o altra brutta cosa.

PS3: Ci cuciture a essere il lavoro nell'ultimo mese per rendere Option aspetto più simile a Traversable senza attuazione: commit, another commit

+2

Per domande di progettazione di linguaggio (o in questo caso libreria) come questa, in genere è meglio chiedere direttamente ai progettisti della lingua (o della biblioteca). Sono abbastanza reattivi sulla mailing list ma solo occasionalmente visitano StackOverflow. –

+4

@ JörgWMittag Sarebbe bello avere una risposta compilata su SO, anche se .. –

risposta

5

Potrebbero esserci problemi con l'flatMap restituire un Option anziché un Traversable. Anche se questo precede l'intero 2.8 CanBuildFrom macchinario.

La domanda era asked una volta prima sulla mailing list ma non ha suscitato una risposta.

Ecco un esempio:

sealed trait OptionX[+A] extends Traversable[A] { 
    def foreach[U](f: (A) => U): Unit = if (!isEmpty) f(get) 
    def get: A 
    def isDefined: Boolean 
    def getOrElse[B >: A](default: => B): B 
} 

case class SomeX[+A](a: A) extends OptionX[A] { 
    override def isEmpty = false 
    def get = a 
    def isDefined = true 
    def getOrElse[B >: A](default: => B) = a 
} 

case object NoneX extends OptionX[Nothing] { 
    override def isEmpty = true 
    def get = sys.error("none") 
    def isDefined = false 
    def getOrElse[B](default: => B) = default 
} 

object O extends App { 
    val s: OptionX[Int] = SomeX(1) 
    val n: OptionX[Int] = NoneX 
    s.foreach(i => println("some " + i)) 
    n.foreach(i => println("should not print " + i)) 
    println(s.map(_ + "!")) 
} 

L'ultima riga restituisce un List("1!") invece di Option. Qualcuno potrebbe inventare uno CanBuildFrom che produrrebbe un SomeX("1!"). Il mio tentativo non è riuscito:

object OptionX { 
    implicit def canBuildFrom[Elem] = new CanBuildFrom[Traversable[_], Elem, OptionX[Elem]] { 
    def builder() = new Builder[Elem, OptionX[Elem]] { 
     var current: OptionX[Elem] = NoneX 
     def +=(elem: Elem): this.type = { 
     if (current.isDefined) sys.error("already defined") 
     else current = SomeX(elem) 
     this 
     } 
     def clear() { current = NoneX } 
     def result(): OptionX[Elem] = current 
    } 
    def apply() = builder() 
    def apply(from: Traversable[_]) = builder() 
    } 
} 

ho bisogno di passare l'implicito esplicitamente:

scala> import o._ 
import o._ 

scala> val s: OptionX[Int] = SomeX(1) 
s: o.OptionX[Int] = SomeX(1) 

scala> s.map(_+1)(OptionX.canBuildFrom[Int]) 
res1: o.OptionX[Int] = SomeX(2) 

scala> s.map(_+1) 
res2: Traversable[Int] = List(2) 

Edit:

Così sono stato in grado di risolvere il problema e hanno un SomeX(1).map(1+) ritorno OptionX con OptionX estendere TraversableLike[A, OptionX[A]] e ignorare newBuilder.

Ma poi ottengo errori di runtime su SomeX(1) ++ SomeX(2) o for (i <- SomeX(1); j <- List(1,2)) yield (i+j). Quindi non penso sia possibile avere l'opzione estendere Traversable e fare qualcosa di sano in termini di restituzione del tipo più specifico.

Oltre alla fattibilità, lo stile di codifica è saggio, non sono sicuro che sia una buona cosa avere Option come un Traversable in tutte le circostanze. Option rappresentano valori che non sono sempre definiti, mentre Traversable definisce i metodi per le raccolte che possono avere più elementi come drop(n), splitAt(n), take(n), ++. Anche se offrirebbe convenienza se Option fosse anche un Traversable, penso che potrebbe rendere l'intento meno chiaro.

Utilizzare un toSeq dove necessario sembra un modo indolore per indicare che voglio che la mia opzione si comporti come un Traversable. E per alcuni casi di utilizzo ricorrenti, v'è la option2Iterable conversione implicita - così per esempio questo funziona già (tutti ritornano List(1,2)):

  • List(Option(1), Option(2), None).flatten
  • for (i <- List(0,1); j <- Some(1)) yield (i+j)
  • Some(1) ++ Some(2)
+0

bella risposta, grazie – shellholic

1

La ragione è che in alcuni casi con impliciti applicato il tipo otterrebbe meno preciso. Avresti comunque un valore Option, ma il tipo di ritorno statico sarebbe qualcosa come Iterable, e. g. non quello "più preciso".

1

Forse Sono denso, ma non capisco perché qualcuno abbia bisogno di questo. Inoltre, richiederebbe None per essere Traversable che sembra semanticamente dubbio.

Dicono che il design è finito non quando non c'è più nulla da aggiungere, ma piuttosto nulla da togliere. Il che non vuol dire, ovviamente, che la libreria standard di Scala sia perfetta.

+1

'Nil' è' Traversable' – shellholic

+0

Touché. Tuttavia 'Nil' e' None' significano cose completamente diverse. 'Nil' estende' Elenco [Nothing] 'ed è il valore di' List() '. Se 'Nil' non fosse' Traversable' causerebbe molti problemi :) –

+0

Cosa dire di 'Future.traverse (opzione) (f)' o 'Future.sequence (opzione)'. Penso che si possa ugualmente affermare che la citazione di Antoine de Saint-Exupéry è un argomento a favore dell'opzione "Opzione" come "Traversabile". In questo momento abbiamo "a" Traversabili' è una cosa che puoi attraversare, eccetto per una 'Opzione' che ha quasi tutto il comportamento di un 'Traversable' ma che non è uno quindi dovresti avere metodi separati per' Opzione e ogni altro tipo di collezione. " La cosa che abbiamo aggiunto è una distinzione arbitraria che non esiste logicamente. –

4

Non è Traversable perché non è possibile implementare uno scala.collection.mutable.Builder per esso.

Beh, potrebbe essere un Traversable anche così, ma ciò comporterebbe un sacco di metodi che restituiscono Option ora restituendo Traversable invece. Se vuoi vedere quali sono questi metodi, guarda i metodi che prendono un parametro CanBuildFrom.

Prendiamo il codice di esempio per dimostrare il motivo per cui:

Seq(Set(1,3,2),Seq(4),Option(5)).flatten 

che dovrebbe essere pari a:

Seq(1, 2, 3, 4, 5) 

Ora, prendiamo in considerazione questa alternativa:

Option(Set(1,3,2),Seq(4),Option(5)).flatten 

Qual è la valore di quello?

+2

L'opzione # applica applica un parametro e la risposta in tal caso è: 'Opzione (Imposta (1,3,2)). Flatten => Imposta (1,3,2)' – shellholic