Co to jest "lifting" w Scali?

Czasami, gdy czytam artykuły w ekosystemie Scali, czytam termin "lifting" / "lifting". Niestety, nie jest wyjaśnione, co to dokładnie oznacza. Zrobiłem trochę badań i wydaje się, że podnoszenie ma coś wspólnego z wartościami funkcjonalnymi lub czymś w tym rodzaju, ale nie byłem w stanie znaleźć tekstu, który wyjaśnia, co tak naprawdę chodzi o podnoszenie w przyjazny dla początkujących sposób.

Istnieje dodatkowe zamieszanie poprzez framework Lift , który ma w nazwie Lift, ale to nie pomaga w odpowiedzi na pytanie.

Co to jest "lifting" w Scali?

Author: Peter Mortensen, 2013-07-31

4 answers

Istnieje kilka zastosowań:

Funkcja częściowa

Pamiętaj, że PartialFunction[A, B] jest funkcją zdefiniowaną dla pewnego podzbioru domeny A (zgodnie z metodą isDefinedAt). Możesz" podnieść " PartialFunction[A, B] do Function[A, Option[B]]. Jest to funkcja zdefiniowana przez całość Z A, ale której wartości są typu Option[B]

Odbywa się to poprzez jawne wywołanie metody lift na 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)

Metody

Możesz "podnieść" wywołanie metody do funkcja. To się nazywa eta-ekspansja (Dzięki Benowi Jamesowi za to). Tak na przykład:

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

Podnosimy metodę do funkcji, stosując podkreślenie

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

scala> f(4)
res0: Int = 8

Zwróć uwagę na podstawową różnicę między metodami i funkcjami. res0 jest instancją (tzn. jest to wartość ) typu (function) (Int => Int)

Funktory

A funktor (zdefiniowany przez scalaz) jest jakimś "kontenerem" (używam terminu ekstremalnie luźno), F takie, że jeśli mamy F[A] i funkcję A => B, to możemy dostać w swoje ręce F[B] (pomyślmy na przykład F = List i metodę map)

Możemy zakodować tę właściwość w następujący sposób:

trait Functor[F[_]] { 
  def map[A, B](fa: F[A])(f: A => B): F[B]
}
Jest to izomorficzne z możliwością "podniesienia" funkcji A => B do domeny funktora. Czyli:
def lift[F[_]: Functor, A, B](f: A => B): F[A] => F[B]

To znaczy, jeśli F jest funktorem, A Mamy funkcję A => B, Mamy funkcję F[A] => F[B]. Możesz spróbować i zaimplementuj metodę lift - to dość trywialne.

Transformatory Monad

Jak mówi hcoopz poniżej (i właśnie zdałem sobie sprawę ,że uchroniłoby mnie to przed napisaniem Tony niepotrzebnego kodu), termin "lift" ma również znaczenie w Monad Transformers. Przypomnijmy, że transformatory monad są sposobem "układania" monad na siebie (monady się nie komponują).

Załóżmy więc, że masz funkcję, która zwraca IO[Stream[A]]. To może być zamienione na transformator monad StreamT[IO, A]. Teraz możesz chcieć "podnieść" jakąś inną wartość an IO[B] być może do tego jest również StreamT. Możesz to napisać:

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

Lub to:

iob.liftM[StreamT]

Nasuwa się pytanie: dlaczego chcę przekształcić IO[B] w StreamT[IO, B]?. Odpowiedzią byłoby "wykorzystanie możliwości kompozycji". Powiedzmy, że masz funkcję 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]
 261
Author: oxbow_lakes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-31 10:38:19

Innym zastosowaniem lifting , na który natknąłem się w dokumentach (niekoniecznie związanych ze scalą), jest przeciążenie funkcji z f: A -> B z f: List[A] -> List[B] (lub zestawów, multisetów, ...). Jest to często używane w celu uproszczenia formalizacji, ponieważ wtedy nie ma znaczenia, czy {[5] } jest stosowany do pojedynczego elementu, czy do wielu elementów.

Tego rodzaju przeciążenia są często wykonywane deklaratywnie, np.

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

Lub

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

Lub imperatywnie, np.

f: List[A] -> List[B]
f(xs) = xs map f
 18
Author: Malte Schwerhoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-31 08:43:10

Zwróć uwagę, że każdy zbiór, który rozszerza PartialFunction[Int, A] (jak wskazuje oxbow_lakes), może zostać zniesiony; tak na przykład

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

, która zamienia funkcję częściową w funkcję całkowitą, gdzie wartości nie zdefiniowane w zbiorze są odwzorowywane na None,

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

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

Ponadto,

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

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

To pokazuje schludne podejście do unikania indeksu poza granicami WYJĄTKÓW.

 16
Author: elm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-21 08:01:14

Istnieje równieżunlifting , który jest procesem odwrotnym do podnoszenia.

Jeśli lifting jest zdefiniowany jako

Przekształcenie funkcji częściowej PartialFunction[A, B] w całkowitą function A => Option[B]

Wtedy unlifting jest

Przekształcenie funkcji całkowitej A => Option[B] w funkcję częściową PartialFunction[A, B]

Biblioteka Standardowa Scala definiuje Function.unlift as

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

Na przykład Biblioteka play-json zapewnia unlift, aby pomóc z konstrukcją seriali 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))
 5
Author: Mario Galic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-04 15:22:37