¿Qué es "levantar" en Scala?

253

A veces, cuando leo artículos en el ecosistema de Scala, leo el término "levantar" / "levantar". Desafortunadamente, no se explica qué significa eso exactamente. Investigué un poco, y parece que el levantamiento tiene algo que ver con valores funcionales o algo así, pero no pude encontrar un texto que explique de qué se trata el levantamiento de una manera amigable para principiantes.

Existe una confusión adicional a través del marco de elevación que tiene elevación en su nombre, pero no ayuda a responder la pregunta.

¿Qué es "levantar" en Scala?

user573215
fuente

Respuestas:

290

Hay algunos usos:

Función parcial

Recuerde que a PartialFunction[A, B]es una función definida para algún subconjunto del dominio A(según lo especificado por el isDefinedAtmétodo). Puedes "levantar" PartialFunction[A, B]a a Function[A, Option[B]]. Es decir, una función definida sobre el conjunto de Apero cuyos valores son de tipoOption[B]

Esto se realiza mediante la invocación explícita del método liften 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)

Métodos

Puede "elevar" una invocación de método a una función. Esto se llama eta-expansión (gracias a Ben James por esto). Así por ejemplo:

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

Elevamos un método a una función aplicando el guión bajo

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

scala> f(4)
res0: Int = 8

Tenga en cuenta la diferencia fundamental entre métodos y funciones. res0es una instancia (es decir, es un valor ) del tipo (función)(Int => Int)

Functores

Un functor (como lo define scalaz ) es un "contenedor" (yo uso el término de forma extremadamente flexible), de Fmodo que, si tenemos una F[A]y una función A => B, podemos tener en nuestras manos un F[B](piense, por ejemplo, F = Listy el mapmétodo )

Podemos codificar esta propiedad de la siguiente manera:

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

Esto es isomorfo para poder "elevar" la función A => Bal dominio del functor. Es decir:

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

Es decir, si Fes un functor, y tenemos una función A => B, tenemos una función F[A] => F[B]. Puede intentar implementar el liftmétodo, es bastante trivial.

Transformadores de mónada

Como dice hcoopz a continuación (y me acabo de dar cuenta de que esto me habría salvado de escribir una tonelada de código innecesario), el término "levantar" también tiene un significado dentro de Monad Transformers . Recuerde que los transformadores de una mónada son una forma de "apilar" mónadas una encima de la otra (las mónadas no se componen).

Entonces, por ejemplo, suponga que tiene una función que devuelve un IO[Stream[A]]. Esto se puede convertir al transformador de mónada StreamT[IO, A]. Ahora es posible que desee "elevar" algún otro valor IO[B]tal vez a que también sea un StreamT. Puedes escribir esto:

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

O esto:

iob.liftM[StreamT]

esto plantea la pregunta: ¿por qué quiero convertir un IO[B]en a StreamT[IO, B]? . La respuesta sería "aprovechar las posibilidades de composición". Digamos que tienes una funciónf: (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]
oxbow_lakes
fuente
12
Vale la pena mencionar que "elevar un método a una función" a menudo se conoce como eta-expansión .
Ben James
77
Profundizando más en scalaz , el levantamiento también aparece en relación con los transformadores de mónada . Si tengo una MonadTransinstancia Tpara My una Monadinstancia para N, entonces T.liftMse puede usar para elevar un valor de tipo N[A]a un valor de tipo M[N, A].
846846846
Gracias Ben, hcoopz. He modificado la respuesta
oxbow_lakes
¡Perfecto! Solo una razón más para decir: Scala, el mejor. Lo que podría llevarse a Martin Odersky & Co, el mejor. Incluso lo usaría liftMpara eso, pero no pude entender cómo hacerlo correctamente. Chicos, eres rock!
Dmitry Bespalov
3
En la sección Métodos ... res0 es una instancia (es decir, es un valor) del tipo (función) (Int => Int) ... ¿No debería fser una instancia, no res0?
srzhio
21

Otro uso del levantamiento que he encontrado en los documentos (no necesariamente relacionados con Scala) es sobrecargar una función f: A -> Bcon f: List[A] -> List[B](o conjuntos, conjuntos múltiples, ...). Esto se usa a menudo para simplificar las formalizaciones porque no importa si fse aplica a un elemento individual o a varios elementos.

Este tipo de sobrecarga a menudo se realiza de forma declarativa, por ejemplo,

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, por ejemplo,

f: List[A] -> List[B]
f(xs) = xs map f
Malte Schwerhoff
fuente
55
Este es el "levantamiento hacia un functor" que describe oxbow_lakes.
Ben James
77
@BenJames Cierto de hecho. En mi defensa: la respuesta de oxbow_lakes todavía no estaba allí cuando comencé a escribir la mía.
Malte Schwerhoff
20

Tenga en cuenta que cualquier colección que se extienda PartialFunction[Int, A](como lo señala oxbow_lakes) puede levantarse; así por ejemplo

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

que convierte una función parcial en una función total donde se asignan valores no definidos en la colección None,

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

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

Además,

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

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

Esto muestra un enfoque ordenado para evitar excepciones de índice fuera de límites .

olmo
fuente
6

También hay unlifting , que es el proceso inverso al levantamiento.

Si el levantamiento se define como

Convertir una función parcial PartialFunction[A, B]en una función totalA => Option[B]

entonces no levantar es

Convertir una función total A => Option[B]en una función parcial PartialFunction[A, B]

La biblioteca estándar de Scala define Function.unliftcomo

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

Por ejemplo, la biblioteca play-json proporciona unlift para ayudar con la construcción de los serializadores 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))
Mario Galic
fuente