287 votes

Qu'est-ce que le "lifting" en Scala ?

Parfois, lorsque je lis des articles dans l'écosystème Scala, je lis le terme "lifting" / "lifted". Malheureusement, il n'est pas expliqué ce que cela signifie exactement. J'ai fait quelques recherches, et il semble que le lifting a quelque chose à voir avec les valeurs fonctionnelles ou quelque chose comme ça, mais je n'ai pas été en mesure de trouver un texte qui explique ce que le lifting signifie réellement d'une manière conviviale pour les débutants.

Il existe une confusion supplémentaire à travers le Ascenseur qui a le mot lifting dans son nom, mais il ne permet pas de répondre à la question.

Qu'est-ce que le "lifting" en Scala ?

321voto

oxbow_lakes Points 70013

Il y a quelques usages :

Fonction partielle

Souvenez-vous d'un PartialFunction[A, B] est une fonction définie pour un certain sous-ensemble du domaine A (comme spécifié par le isDefinedAt ). Vous pouvez "lever" un PartialFunction[A, B] en un Function[A, Option[B]] . C'est-à-dire qu'une fonction définie sur le todo de A mais dont les valeurs sont de type Option[B]

Cela se fait par l'invocation explicite de la méthode lift en 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éthodes

Vous pouvez "lever" une invocation de méthode dans une fonction. Cela s'appelle eta-expansion (merci à Ben James pour cela). Ainsi, par exemple :

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

Nous transformons une méthode en une fonction en appliquant la fonction soulignement

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

scala> f(4)
res0: Int = 8

Notez la différence fondamentale entre les méthodes et les fonctions. res0 es un instance (c'est-à-dire qu'il s'agit d'un valeur ) du type (fonction) (Int => Int)

Foncteurs

A foncteur (tel que défini par scalaz ) est un certain "conteneur" (j'utilise le terme extrêmement en vrac), F de sorte que, si nous avons un F[A] et une fonction A => B alors nous pouvons mettre la main sur un F[B] (pensez, par exemple, F = List et le map méthode)

Nous pouvons coder cette propriété comme suit :

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

Ceci est isomorphe au fait de pouvoir "lever" la fonction A => B dans le domaine du foncteur. C'est-à-dire :

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

C'est-à-dire que si F est un foncteur, et nous avons une fonction A => B nous avons une fonction F[A] => F[B] . Vous pourriez essayer de mettre en œuvre la lift c'est assez trivial.

Transformateurs de monades

Comme hcoopz dit ci-dessous (et je viens de me rendre compte que cela m'aurait évité d'écrire une tonne de code inutile), le terme "ascenseur" a également une signification au sein de Transformateurs de monades . Rappelons que les transformateurs de monades sont un moyen d'"empiler" des monades les unes sur les autres (les monades ne se composent pas).

Par exemple, supposons que vous ayez une fonction qui renvoie un IO[Stream[A]] . Ceci peut être converti en transformateur de monade StreamT[IO, A] . Maintenant, vous pouvez souhaiter "lever" une autre valeur, un IO[B] peut-être à cela qu'il est aussi un StreamT . Vous pourriez soit écrire ceci :

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

Ou ça :

iob.liftM[StreamT]

cela pose la question : pourquoi je veux convertir un IO[B] en un StreamT[IO, B] ? . La réponse serait "pour tirer parti des possibilités de composition". Disons que vous avez une fonction f: (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]

13 votes

Il peut être utile de mentionner que "élever une méthode à une fonction" est souvent appelé eta-expansion .

7 votes

En approfondissant la question scalaz , levage se pose également en relation avec transformateurs de monades . Si j'ai un MonadTrans instance T pour M et un Monad instance pour N entonces T.liftM peut être utilisé pour ascenseur une valeur de type N[A] à une valeur de type M[N, A] .

0 votes

Merci Ben, hcoopz. J'ai modifié la réponse

27voto

enzyme Points 2347

Notez que toute collection qui étend PartialFunction[Int, A] (comme l'a souligné oxbow_lakes) peuvent être levés ; ainsi par exemple

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

qui transforme une fonction partielle en une fonction totale où les valeurs non définies dans la collection sont mappées sur None ,

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

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

De plus,

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

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

Cela montre une approche soignée pour éviter index hors limites exceptions.

21voto

Malte Schwerhoff Points 5713

Une autre utilisation de levage que j'ai rencontré dans des articles (pas nécessairement liés à Scala) est la surcharge d'une fonction à partir de f: A -> B con f: List[A] -> List[B] (ou ensembles, multisets, ...). Ceci est souvent utilisé pour simplifier les formalisations car il n'est alors pas important de savoir si f est appliquée à un élément individuel ou à plusieurs éléments.

Ce type de surcharge est souvent effectué de manière déclarative, par exemple,

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))

ou impérativement, par exemple,

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

5 votes

C'est le "lifting into a functor" que décrit oxbow_lakes.

7 votes

@BenJames Vrai en effet. Pour ma défense : la réponse d'oxbow_lakes n'était pas encore là quand j'ai commencé à écrire la mienne.

6voto

Mario Galic Points 3246

Il y a également déstockage qui est le processus inverse de la levée.

Si le levage est défini comme

transformer une fonction partielle PartialFunction[A, B] en un total fonction A => Option[B]

alors le soulèvement est

la transformation d'une fonction totale A => Option[B] en une fonction partielle PartialFunction[A, B]

La bibliothèque standard de Scala définit Function.unlift comme

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

Par exemple, la bibliothèque play-json fournit déverrouiller pour aider à la construction de Sérialiseurs 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))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X