O que é "levantamento" no Scala?

253

Às vezes, quando leio artigos no ecossistema Scala, leio o termo "levantamento" / "levantado". Infelizmente, não está explicado o que isso significa exatamente. Eu fiz algumas pesquisas e parece que o levantamento tem algo a ver com valores funcionais ou algo assim, mas não consegui encontrar um texto que explique o que é realmente o levantamento de uma maneira amigável para iniciantes.

Há uma confusão adicional por meio da estrutura do Lift , que possui o lifting em seu nome, mas não ajuda a responder à pergunta.

O que é "levantamento" no Scala?

user573215
fonte

Respostas:

290

Existem alguns usos:

PartialFunction

Lembre-se de a PartialFunction[A, B]é uma função definida para algum subconjunto do domínio A(conforme especificado pelo isDefinedAtmétodo). Você pode "elevar" a PartialFunction[A, B]para a Function[A, Option[B]]. Ou seja, uma função definida sobre o conjunto de Amas cujos valores são do tipoOption[B]

Isso é feito pela invocação explícita do método 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)

Métodos

Você pode "elevar" uma chamada de método para uma função. Isso se chama eta-expansão (obrigado a Ben James por isso). Então, por exemplo:

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

Colocamos um método em uma função aplicando o sublinhado

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

scala> f(4)
res0: Int = 8

Observe a diferença fundamental entre métodos e funções. res0é uma instância (ou seja, é um valor ) do tipo (função)(Int => Int)

Functors

Um functor (como definido por scalaz ) é algum "contêiner" (eu uso o termo extremamente livremente), de Fmodo que, se tivermos F[A]uma função e A => B, então podemos colocar a mão em um F[B](pense, por exemplo, F = Liste no mapmétodo )

Podemos codificar esta propriedade da seguinte maneira:

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

Isso é isomórfico por poder "elevar" a função A => Bpara o domínio do functor. Isso é:

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

Ou seja, se Fé um functor, e temos uma função A => B, temos uma função F[A] => F[B]. Você pode tentar implementar o liftmétodo - é bastante trivial.

Monad Transformers

Como o hcoopz diz abaixo (e eu acabei de perceber que isso me salvaria de escrever uma tonelada de código desnecessário), o termo "lift" também tem um significado dentro dos Monad Transformers . Lembre-se de que os transformadores de mônada são uma maneira de "empilhar" mônadas umas sobre as outras (as mônadas não compõem).

Por exemplo, suponha que você tenha uma função que retorne um IO[Stream[A]]. Isso pode ser convertido no transformador de mônada StreamT[IO, A]. Agora você pode querer "elevar" algum outro valor e IO[B]talvez também seja um StreamT. Você pode escrever isto:

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

Ou isto:

iob.liftM[StreamT]

isso levanta a questão: por que eu quero converter um IO[B]em um StreamT[IO, B]? . A resposta seria "tirar proveito das possibilidades de composição". Digamos que você tenha uma funçãof: (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
fonte
12
Vale a pena mencionar que "elevar um método a uma função" é freqüentemente chamado de eta-expansão .
Ben James
7
Mergulhando ainda mais no scalaz , o levantamento também surge em relação aos transformadores de mônada . Se eu tiver uma MonadTransinstância Tpara Me um Monadexemplo para N, em seguida, T.liftMpode ser usado para levantar um valor de tipo N[A]para um valor do tipo M[N, A].
846846846
Obrigado Ben, hcoopz. Eu modifiquei a resposta
oxbow_lakes 31/07
Perfeito! Apenas mais um motivo para dizer: Scala - o melhor. O que poderia ser levantado para a Martin Odersky & Co - a melhor. Eu até usaria liftMpara isso, mas não conseguia entender como fazer isso corretamente. Gente, você é rock!
Dmitry Bespalov
3
Na seção Métodos ... res0 é uma instância (ou seja, é um valor) do tipo (função) (Int => Int) ... Não deve fser uma instância, não é res0?
srzhio ​​8/0118
21

Outro uso de levantamento que encontrei nos documentos (não necessariamente relacionados ao Scala) está sobrecarregando uma função f: A -> Bcom f: List[A] -> List[B](ou conjuntos, multisets, ...). Isso geralmente é usado para simplificar formalizações, pois não importa se fé aplicado a um elemento individual ou a vários elementos.

Esse tipo de sobrecarga geralmente é feito de forma declarativa, por exemplo,

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 imperativamente, por exemplo,

f: List[A] -> List[B]
f(xs) = xs map f
Malte Schwerhoff
fonte
5
Este é o "levantamento para um functor" que oxbow_lakes descreve.
Ben James
7
@BenJames True, de fato. Para minha defesa: a resposta de oxbow_lakes ainda não estava lá quando comecei a escrever a minha.
Malte Schwerhoff
20

Observe que qualquer coleção que se estenda PartialFunction[Int, A](como indicado por oxbow_lakes) pode ser levantada; assim, por exemplo

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

que transforma uma função parcial em uma função total na qual os valores não definidos na coleção são mapeados None,

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

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

Além disso,

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

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

Isso mostra uma abordagem elegante para evitar exceções fora dos limites do índice .

olmo
fonte
6

Também há o levantamento , que é o processo inverso do levantamento.

Se o levantamento for definido como

transformando uma função parcial PartialFunction[A, B]em uma função totalA => Option[B]

então a remoção é

transformando uma função total A => Option[B]em uma função parcial PartialFunction[A, B]

A biblioteca padrão Scala define Function.unliftcomo

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

Por exemplo, a biblioteca play-json fornece unfift para ajudar na construção de serialisers 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))
Mario Galic
fonte