Что такое «лифтинг» в Scala?

253

Иногда, когда я читаю статьи в экосистеме Scala, я читаю термин «поднятие» / «поднятие». К сожалению, это не объясняется, что именно это означает. Я провел некоторое исследование, и кажется, что лифтинг как-то связан с функциональными ценностями или чем-то в этом роде, но я не смог найти текст, который объясняет, что такое лифтинг на самом деле для начинающих.

Существует дополнительная путаница в структуре Lift, которая имеет название подъема, но это не помогает ответить на вопрос.

Что такое «лифтинг» в Scala?

user573215
источник

Ответы:

290

Есть несколько способов использования:

частично определённая функция

Помните, что PartialFunction[A, B]это функция, определенная для некоторого подмножества домена A(как указано в isDefinedAtметоде). Вы можете «поднять» PartialFunction[A, B]в Function[A, Option[B]]. То есть, функция , определенная над всеми из , Aно значения которых имеют типаOption[B]

Это делается путем явного вызова метода lifton 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)

методы

Вы можете «поднять» вызов метода в функцию. Это называется eta-extension (спасибо Бену Джеймсу за это). Так, например:

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

Мы поднимаем метод в функцию, применяя подчеркивание

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

scala> f(4)
res0: Int = 8

Обратите внимание на фундаментальное различие между методами и функциями. res0это экземпляр (т.е. это значение ) типа (функция)(Int => Int)

ФУНКТОРЫ

Функтор (как определено scalaz ) некоторые «контейнер» (я использую этот термин очень затягивая), Fтаким образом, что, если у нас есть F[A]и функции A => B, то мы можем получить в свои руки F[B](вспомним, например, F = Listи mapметод )

Мы можем закодировать это свойство следующим образом:

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

Это изоморфно возможности «поднять» функцию A => Bв область функтора. То есть:

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

То есть, если Fэто функтор, и у нас есть функция A => B, у нас есть функция F[A] => F[B]. Вы можете попробовать реализовать liftметод - это довольно тривиально.

Монад Трансформеры

Как говорит hcoopz ниже (и я только что понял, что это избавило бы меня от написания тонны ненужного кода), термин «лифт» также имеет значение в Monad Transformers . Напомним, что монадные трансформеры являются способом «укладывания» монад друг на друга (монады не составляют).

Например, предположим, у вас есть функция, которая возвращает IO[Stream[A]]. Это можно преобразовать в монадный трансформатор StreamT[IO, A]. Теперь вы можете захотеть «поднять» какую-то другую ценность, IO[B]возможно, на то, что она также является StreamT. Вы можете написать это:

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

Или это:

iob.liftM[StreamT]

напрашивается вопрос: почему я хочу преобразовать IO[B]в StreamT[IO, B]? , Ответ будет «использовать возможности композиции». Допустим, у вас есть функция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]
oxbow_lakes
источник
12
Возможно, стоит упомянуть, что «поднятие метода в функцию» часто называют eta-расширением .
Бен Джеймс
7
Погружаясь дальше в скалаз , лифтинг также подходит по отношению к монадным трансформаторам . Если у меня есть MonadTransэкземпляр Tдля Mи Monadэкземпляр для N, то T.liftMможно использовать для поднятия значения типа N[A]до значения типа M[N, A].
846846846
Спасибо, Бен. Я изменил ответ
oxbow_lakes
Отлично! Еще одна причина сказать: Скала - лучшая. Который может быть поднят к Martin Odersky & Co - лучший. Я бы даже использовал liftMдля этого, но не сумел понять, как это сделать правильно. Ребята, вы рок!
Дмитрий Беспалов
3
В разделе « Методы » ... res0 - это экземпляр (то есть значение) типа (функция) (Int => Int) ... Не должен ли это fбыть экземпляр, не так res0ли?
Сржио
21

Другое использование подъема, с которым я сталкивался в статьях (не обязательно связанных с Scala), - это перегрузка функции с f: A -> Bпомощью f: List[A] -> List[B](или множеств, мультимножеств, ...). Это часто используется для упрощения формализации, потому что тогда не имеет значения, fприменяется ли он к отдельному элементу или к нескольким элементам.

Этот вид перегрузки часто выполняется декларативно, например,

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

или

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

или обязательно, например,

f: List[A] -> List[B]
f(xs) = xs map f
Малте Шверхофф
источник
5
Это «подъем в функтор», который описывает oxbow_lakes.
Бен Джеймс
7
@BenJames Правда, правда. В мою защиту: ответа oxbow_lakes еще не было, когда я начал писать свой.
Малте Шверхофф
20

Обратите внимание, что любая коллекция, которая расширяется PartialFunction[Int, A](как указано oxbow_lakes), может быть отменена; таким образом, например

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

которая превращает частичную функцию в целую функцию, в которую отображаются значения, не определенные в коллекции None,

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

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

Более того,

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

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

Это показывает аккуратный подход, чтобы избежать исключений индекса за пределами границ .

вяз
источник
6

Существует также подъем , который является обратным процессом для подъема.

Если лифтинг определяется как

превращение частичной функции PartialFunction[A, B]в общую функциюA => Option[B]

тогда unlifting

превращение полной функции A => Option[B]в частичную функцию PartialFunction[A, B]

Стандартная библиотека Scala определяет Function.unliftкак

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

Например, библиотека play-json предоставляет unlift для помощи в создании сериализаторов 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))
Марио Галич
источник