Cosa significa "sollevare" in Scala?


253

A volte, quando leggo articoli nell'ecosistema Scala, leggo il termine "sollevamento" / "sollevato". Sfortunatamente, non viene spiegato cosa significhi esattamente. Ho fatto alcune ricerche e sembra che il sollevamento abbia qualcosa a che fare con i valori funzionali o qualcosa del genere, ma non sono stato in grado di trovare un testo che spieghi di cosa tratta effettivamente il sollevamento in modo amichevole per i principianti.

Vi è ulteriore confusione attraverso il framework Lift che ha sollevato il suo nome, ma non aiuta a rispondere alla domanda.

Cosa significa "sollevare" in Scala?

Risposte:


290

Ci sono alcuni usi:

funzione parziale

Ricorda che a PartialFunction[A, B]è una funzione definita per alcuni sottogruppi del dominio A(come specificato dal isDefinedAtmetodo). Puoi "sollevare" a PartialFunction[A, B]in a Function[A, Option[B]]. Cioè, una funzione definita sul intero di Ama i cui valori sono di tipoOption[B]

Questo viene fatto dall'invocazione esplicita del metodo liftsu PartialFunction.

scala> val pf: PartialFunction[Int, Boolean] = { case i if i > 0 => i % 2 == 0}
pf: PartialFunction[Int,Boolean] = <function1>

scala> pf.lift
res1: Int => Option[Boolean] = <function1>

scala> res1(-1)
res2: Option[Boolean] = None

scala> res1(1)
res3: Option[Boolean] = Some(false)

metodi

È possibile "sollevare" l'invocazione di un metodo in una funzione. Questo si chiama eta-espansione (grazie a Ben James per questo). Quindi per esempio:

scala> def times2(i: Int) = i * 2
times2: (i: Int)Int

Solleviamo un metodo in una funzione applicando il trattino basso

scala> val f = times2 _
f: Int => Int = <function1>

scala> f(4)
res0: Int = 8

Nota la differenza fondamentale tra metodi e funzioni. res0è un'istanza (ovvero è un valore ) del tipo (funzione)(Int => Int)

funtori

Un functor (come definito da scalaz ) è un "contenitore" (uso il termine in modo molto approssimativo), in modo Ftale che, se abbiamo una F[A]e una funzione A => B, possiamo mettere le mani su un F[B](pensa, per esempio, F = Liste il mapmetodo )

Possiamo codificare questa proprietà come segue:

trait Functor[F[_]] { 
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

Questo è isomorfo per essere in grado di "sollevare" la funzione A => Bnel dominio del funzione. Questo è:

def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]

Cioè, se Fè un funzione e abbiamo una funzione A => B, abbiamo una funzione F[A] => F[B]. Potresti provare ad implementare il liftmetodo - è piuttosto banale.

Trasformatori di monade

Come dice hcoopz di seguito (e mi sono appena reso conto che questo mi avrebbe salvato dalla scrittura di un sacco di codice non necessario), il termine "ascensore" ha anche un significato all'interno di Monad Transformers . Ricordiamo che i trasformatori di una monade sono un modo per "impilare" le monadi l'una sopra l'altra (le monadi non si compongono).

Quindi, ad esempio, supponiamo di avere una funzione che restituisce un IO[Stream[A]]. Questo può essere convertito nel trasformatore di monade StreamT[IO, A]. Ora potresti voler "sollevare" qualche altro valore e IO[B]forse anche quello StreamT. Puoi scrivere questo:

StreamT.fromStream(iob map (b => Stream(b)))

O questo:

iob.liftM[StreamT]

questo fa sorgere la domanda: perché voglio convertire un IO[B]in un StreamT[IO, B]? . La risposta sarebbe "sfruttare le possibilità di composizione". Diciamo che hai una funzionef: (A, B) => C

lazy val f: (A, B) => C = ???
val cs = 
  for {
    a <- as                //as is a StreamT[IO, A]
    b <- bs.liftM[StreamT] //bs was just an IO[B]
  }
  yield f(a, b)

cs.toStream //is a Stream[IO[C]], cs was a StreamT[IO, C]

12
Vale la pena ricordare che "elevare un metodo a una funzione" viene spesso definito espansione eta .
Ben James,

7
Scavando ulteriormente nello scalaz , il sollevamento arriva anche in relazione ai trasformatori di monade . Se ho MonadTransun'istanza Tper Me aMonad un'istanza per N, allora T.liftMposso essere usato per elevare un valore di tipo N[A]a un valore di tipo M[N, A].
846846846

Grazie Ben, ciao. Ho modificato la risposta
oxbow_lakes

Perfetto! Un motivo in più per dire: Scala - il migliore. Che potrebbe essere portato a Martin Odersky & Co - il migliore. Vorrei anche usarlo liftMper quello, ma non sono riuscito a capire come farlo correttamente. Ragazzi, siete rock!
Dmitry Bespalov,

3
Nel sezione Metodi ... res0 è un'istanza (ovvero è un valore) del tipo (funzione) (Int => Int) ... Non dovrebbe fessere un'istanza, no res0?
srzhio,

21

Un altro uso del sollevamento che ho riscontrato nei documenti (non necessariamente quelli relativi alla Scala) è il sovraccarico di una funzione f: A -> Bcon f: List[A] -> List[B](o set, multiset, ...). Questo è spesso usato per semplificare le formalizzazioni perché non importa se fviene applicato a un singolo elemento o a più elementi.

Questo tipo di sovraccarico viene spesso eseguito in modo dichiarativo, ad es.

f: List[A] -> List[B]
f(xs) = f(xs(1)), f(xs(2)), ..., f(xs(n))

o

f: Set[A] -> Set[B]
f(xs) = \bigcup_{i = 1}^n f(xs(i))

o imperativamente, ad es.

f: List[A] -> List[B]
f(xs) = xs map f

5
Questo è il "sollevamento in un funzione" descritto da oxbow_lakes.
Ben James,

7
@BenJames Vero davvero. A mia difesa: la risposta di oxbow_lakes non c'era ancora quando ho iniziato a scrivere la mia.
Malte Schwerhoff,

20

Nota: qualsiasi raccolta che si estende PartialFunction[Int, A](come sottolineato da oxbow_lakes) può essere revocata; quindi per esempio

Seq(1,2,3).lift
Int => Option[Int] = <function1>

che trasforma una funzione parziale in una funzione totale in cui sono mappati i valori non definiti nella raccolta None,

Seq(1,2,3).lift(2)
Option[Int] = Some(3)

Seq(1,2,3).lift(22)
Option[Int] = None

Inoltre,

Seq(1,2,3).lift(2).getOrElse(-1)
Int = 3

Seq(1,2,3).lift(22).getOrElse(-1)
Int = -1

Ciò mostra un approccio accurato per evitare eccezioni di indice fuori limite .


6

C'è anche irritante , che è il processo inverso al sollevamento.

Se il sollevamento è definito come

trasformando una funzione parziale PartialFunction[A, B]in una funzione totaleA => Option[B]

allora è irritante

trasformando una funzione totale A => Option[B]in una funzione parziale PartialFunction[A, B]

La libreria standard di Scala definisce Function.unliftcome

def unlift[T, R](f: (T)Option[R]): PartialFunction[T, R]

Ad esempio, la libreria play-json fornisce unlift per aiutare con la costruzione di serializzatori JSON :

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Location(lat: Double, long: Double)

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.