web-dev-qa-db-fra.com

Qu'est-ce que "lever" en Scala?

Parfois, lorsque je lisais des articles dans l’écosystème Scala, je lisais le terme "lever"/"soulevé". Malheureusement, ce que cela signifie exactement n’est pas expliqué. J'ai fait des recherches et il semble que Le levage a quelque chose à voir avec les valeurs fonctionnelles ou quelque chose comme ça, mais je n’ai pas pu trouver un texte qui explique ce qu’est vraiment le levage dans une perspective conviviale pour les débutants.

Il existe une confusion supplémentaire à travers le cadre Lift qui porte le nom levage, mais cela ne résout pas le problème.

Qu'est-ce que "lever" en Scala?

241
user573215

Il y a quelques usages:

PartialFunction

N'oubliez pas que PartialFunction[A, B] Est une fonction définie pour un sous-ensemble du domaine A (comme spécifié par la méthode isDefinedAt.). Vous pouvez "lever" un PartialFunction[A, B] Dans un Function[A, Option[B]]. C'est-à-dire une fonction définie sur l'ensemble de A mais dont les valeurs sont de type Option[B]

Cela se fait par l'appel explicite de la méthode lift sur 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)

Les méthodes

Vous pouvez "lever" un appel de méthode dans une fonction. Ceci s'appelle eta-expansion (merci à Ben James pour cela). Donc par exemple:

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

Nous élevons une méthode dans une fonction en appliquant le trait de 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 Est une instance (c'est-à-dire une valeur ) du type (fonction) (Int => Int)

Foncteurs

A functor (as defined by scalaz) is some "container" (I use the term extremely loosely), F such that, if we have an F[A] and a function A => B, then we can get our hands on an F[B] (think, for example, F = List and the map method)

Nous pouvons encoder cette propriété comme suit:

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

Cela est isomorphe de pouvoir "soulever" la fonction A => B Dans le domaine du foncteur. C'est:

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

Autrement dit, si F est un foncteur et que nous avons une fonction A => B, Nous avons une fonction F[A] => F[B]. Vous pouvez essayer d'implémenter la méthode lift - c'est assez simple.

Transformateurs Monad

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 "lift" aussi a une signification dans Monad Transformers. Rappelez-vous que les transformateurs de monades sont un moyen "d'empiler" des monades les unes sur les autres (les monades ne composent pas).

Donc, par exemple, supposons que vous ayez une fonction qui retourne un IO[Stream[A]]. Cela peut être converti au transformateur monade StreamT[IO, A]. Maintenant, vous voudrez peut-être "lever" une autre valeur, un IO[B]. Peut-être qu’il s’agit également d’un StreamT. Vous pouvez soit écrire ceci:

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

Ou ca:

iob.liftM[StreamT]

cela soulève la question: pourquoi je veux convertir un IO[B] en un StreamT[IO, B]? . La réponse serait "de 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]
278
oxbow_lakes

Une autre utilisation de levée que j’ai rencontrée dans des articles (pas nécessairement liés à Scala) surcharge une fonction de f: A -> B avec f: List[A] -> List[B] (ou ensembles, multisets, ...). Ceci est souvent utilisé pour simplifier les formalisations car peu importe alors que f soit appliqué à 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))

ou

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
21
Malte Schwerhoff

Notez toute collection qui s'étend PartialFunction[Int, A] (comme indiqué par oxbow_lakes) peut être levé; donc 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 les index hors limites exceptions.

19
elm

Il y a aussi unifting , qui est le processus inverse du levage.

Si levage est défini comme

tourner une fonction partielle PartialFunction[A, B] dans une fonction totale A => Option[B]

puis décoller est

tourner une fonction totale A => Option[B] dans une fonction partielle PartialFunction[A, B]

La bibliothèque standard 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 nlift pour vous aider à construire serialiseurs 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))
6
Mario Galic