Was ist "Heben" in Scala?

258

Wenn ich Artikel im Scala-Ökosystem lese, lese ich manchmal den Begriff "Heben" / "Heben". Leider wird nicht erklärt, was das genau bedeutet. Ich habe einige Nachforschungen angestellt, und es scheint, dass das Heben etwas mit funktionalen Werten oder Ähnlichem zu tun hat, aber ich konnte keinen Text finden, der auf anfängerfreundliche Weise erklärt, worum es beim Heben eigentlich geht.

Es gibt zusätzliche Verwirrung durch das Lift- Framework, dessen Name Lifting enthält, aber es hilft nicht, die Frage zu beantworten.

Was ist "Heben" in Scala?

user573215
quelle

Antworten:

297

Es gibt einige Verwendungen:

PartialFunction

Denken Sie daran, dass a PartialFunction[A, B]eine Funktion ist, die für eine Teilmenge der Domäne definiert ist A(wie von der isDefinedAtMethode angegeben). Sie können ein PartialFunction[A, B]in ein "heben" Function[A, Option[B]]. Das heißt, eine Funktion über die definierte ganze von Aaber , deren Werte vom TypOption[B]

Dies erfolgt durch den expliziten Aufruf der Methode liftam 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)

Methoden

Sie können einen Methodenaufruf in eine Funktion "heben". Dies nennt man eta-Expansion (danke an Ben James dafür). Also zum Beispiel:

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

Wir heben eine Methode in eine Funktion auf, indem wir den Unterstrich anwenden

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

scala> f(4)
res0: Int = 8

Beachten Sie den grundlegenden Unterschied zwischen Methoden und Funktionen. res0ist eine Instanz (dh ein Wert ) vom Typ (Funktions-)(Int => Int)

Funktoren

Ein Funktor (wie von scalaz definiert ) ist ein "Container" (ich verwende den Begriff extrem locker), Fso dass wir, wenn wir eine F[A]und eine Funktion haben A => B, einen in die Hände bekommen können F[B](denken Sie zum Beispiel an F = Listdie mapMethode) )

Wir können diese Eigenschaft wie folgt codieren:

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

Dies ist isomorph, um die Funktion A => Bin die Domäne des Funktors "heben" zu können . Das ist:

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

Das heißt, wenn Fes sich um einen Funktor handelt und wir eine Funktion haben A => B, haben wir eine Funktion F[A] => F[B]. Sie könnten versuchen, die liftMethode zu implementieren - sie ist ziemlich trivial.

Monadentransformatoren

Wie hcoopz weiter unten sagt (und ich habe gerade festgestellt, dass ich dadurch nicht eine Menge unnötigen Codes geschrieben hätte), hat der Begriff "Lift" auch innerhalb von Monad Transformers eine Bedeutung . Denken Sie daran, dass Monadentransformatoren eine Möglichkeit sind, Monaden übereinander zu "stapeln" (Monaden komponieren nicht).

Angenommen, Sie haben eine Funktion, die eine zurückgibt IO[Stream[A]]. Dies kann in den Monadentransformator umgewandelt werden StreamT[IO, A]. Jetzt möchten Sie vielleicht einen anderen Wert "anheben" und IO[B]vielleicht ist es auch ein StreamT. Sie könnten entweder Folgendes schreiben:

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

Oder dieses:

iob.liftM[StreamT]

das wirft die frage auf: warum möchte ich ein IO[B]in ein umwandeln StreamT[IO, B]? . Die Antwort wäre "Kompositionsmöglichkeiten nutzen". Angenommen, Sie haben eine Funktionf: (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
quelle
13
Es könnte erwähnenswert sein, dass "das Anheben einer Methode zu einer Funktion" oft als eta-Expansion bezeichnet wird .
Ben James
7
Wenn man sich weiter mit Skalaz befasst , kommt das Heben auch in Bezug auf Monadentransformatoren zum Tragen . Wenn ich eine habe MonadTransBeispiel Tfür Mund ein MonadBeispiel für N, dann T.liftMkann verwendet werden , um hebt einen Wert vom Typ N[A]auf einen Wert von Typ M[N, A].
846846846
Danke Ben, hcoopz. Ich habe die Antwort geändert
oxbow_lakes
Perfekt! Nur noch ein Grund zu sagen: Scala - das Beste. Was zu Martin Odersky & Co gehoben werden könnte - das Beste. Ich würde es sogar verwenden liftM, habe aber nicht verstanden, wie man das richtig macht. Leute, du bist Rock!
Dmitry Bespalov
3
Im Abschnitt Methoden ... ist res0 eine Instanz (dh ein Wert) vom Typ (Funktion) (Int => Int) ... Sollte keine fInstanz sein, nicht res0?
Srzhio
23

Beachten Sie, dass jede Sammlung, die erweitert wird PartialFunction[Int, A](wie von oxbow_lakes hervorgehoben), aufgehoben werden kann. so zum Beispiel

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

Dadurch wird aus einer Teilfunktion eine Gesamtfunktion, auf die Werte abgebildet werden None, die nicht in der Sammlung definiert sind .

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

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

Außerdem,

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

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

Dies zeigt einen übersichtlichen Ansatz, um Ausnahmen außerhalb des Index zu vermeiden .

Ulme
quelle
21

Eine andere Verwendung des Hebens , auf die ich in Zeitungen gestoßen bin (nicht unbedingt auf Scala-bezogene), ist das Überladen einer Funktion f: A -> Bmit f: List[A] -> List[B](oder Mengen, Multisets, ...). Dies wird häufig verwendet, um Formalisierungen zu vereinfachen, da es dann nicht darauf ankommt, ob fes auf ein einzelnes Element oder auf mehrere Elemente angewendet wird.

Diese Art der Überladung erfolgt häufig deklarativ, z.

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

oder

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

oder unbedingt, z.

f: List[A] -> List[B]
f(xs) = xs map f
Malte Schwerhoff
quelle
5
Dies ist das "Heben in einen Funktor", das oxbow_lakes beschreibt.
Ben James
7
@ BenJames True in der Tat. Zu meiner Verteidigung: Die Antwort von oxbow_lakes war noch nicht da, als ich anfing, meine zu schreiben.
Malte Schwerhoff
6

Es gibt auch ein Aufheben , was der umgekehrte Vorgang zum Anheben ist.

Wenn das Heben definiert ist als

eine Teilfunktion PartialFunction[A, B]in eine Gesamtfunktion verwandelnA => Option[B]

dann ist das Aufheben

eine Gesamtfunktion A => Option[B]in eine Teilfunktion verwandelnPartialFunction[A, B]

Scala Standardbibliothek definiert Function.unlift als

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

Zum Beispiel bietet die play-json-Bibliothek Unlift , um beim Aufbau von JSON-Serialisierern zu helfen :

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
quelle