Perché l'opzione non ha un metodo piega?
-
26-10-2019 - |
Domanda
mi chiedo perché scala.Option
non ha un metodo come questo fold
definito:
fold(ifSome: A => B , ifNone: => B)
equivalente a
map(ifSome).getOrElse(ifNone)
Non c'è di meglio che usare map
+ getOrElse
?
Soluzione
Si può fare:
opt foldLeft (els) ((x, y) => fun(x))
o
(els /: opt) ((x,y) => fun(x))
(Entrambe le soluzioni valuterà els
per valore, che potrebbe non essere ciò che si desidera. Grazie a Rex Kerr per puntare a questo.)
Modifica
Ma che cosa si vuole veramente è di Scalaz catamorphism cata
(in pratica un fold
che non solo le maniglie il valore Some
ma le mappe anche la parte None
, che è quello che hai descritto)
opt.cata(fun, els)
definito come (dove value
è il valore dell'opzione pimped)
def cata[X](some: A => X, none: => X): X = value match {
case None => none
case Some(a) => some(a)
}
, che è equivalente a opt.map(some).getOrElse(none)
.
Anche se devo sottolineare che si dovrebbe utilizzare solo cata quando è il modo ‘più naturale’ di esprimerlo. Ci sono molti casi in cui un semplice può bastare map
-getOrElse
, soprattutto quando si tratta potenzialmente concatenamento un sacco di map
s. (Anche se si potrebbe anche catena fun
s con la composizione di funzione, ovviamente -. Dipende dal fatto che si vuole mettere a fuoco la composizione di funzione o il valore di trasformazione)
Altri suggerimenti
Personalmente trovo metodi come cata
che prendono due chiusure come argomenti sono spesso strafare. Cosa si guadagna davvero in leggibilità oltre map
+ getOrElse
? Pensate a un nuovo arrivato al codice: Cosa faranno fare della
opt cata { x => x + 1, 0 }
pensi davvero che questo è più chiaro che
opt map { x => x + 1 } getOrElse 0
In realtà direi che nessuno dei due è preferibile rispetto il buon vecchio
opt match {
case Some(x) => x + 1
case None => 0
}
Come sempre, c'è un limite in cui l'astrazione aggiuntivo non ti dà benefici e giri controproducente.
È stato infine aggiunto a Scala 2.10 , con la firma fold[B](ifEmpty: => B)(f: A => B): B
.
Purtroppo, questo ha una conseguenza comune negativo: B
viene dedotta per chiamate in base solamente sull'argomento ifEmpty
, che è in pratica spesso più stretto. Per esempio. (Una versione corretta è già nella libreria standard, questo è solo per la dimostrazione)
def toList[A](x: Option[A]) = x.fold(Nil)(_ :: Nil)
Scala sarà inferire B
essere Nil.type
invece di List[A]
desiderato e si lamentano di non tornare f
Nil.type
. Invece, è necessario uno dei
x.fold[List[A]](Nil)(_ :: Nil)
x.fold(Nil: List[A])(_ :: Nil)
Questo rende fold
non del tutto equivalente alla corrispondente match
.
Come menzionato da Debilski, è possibile utilizzare OptionW.cata
o fold
di Scalaz. Come ha commentato Jason, parametri denominati rendono questo aspetto bello:
opt.fold { ifSome = _ + 1, ifNone = 0 }
Ora, se il valore che si desidera nel caso None
è mzero
per qualche Monoid[M]
e si dispone di un f: A => M
funzione per il caso Some
, si può fare in questo modo:
opt foldMap f
opt map (_ + 1) getOrElse 0
diventa ??p>
opt foldMap (_ + 1)
Personalmente, penso che Option
dovrebbe avere un metodo apply
che sarebbe il catamorphism. In questo modo si può solo fare questo:
opt { _ + 1, 0 }
o
opt { some = _ + 1, none = 0 }
In realtà, questo sarebbe bello avere per tutte le strutture di dati algebrica.