Apa itu "mengangkat" di Scala?

253

Terkadang ketika saya membaca artikel di ekosistem Scala saya membaca istilah "mengangkat" / "mengangkat". Sayangnya, tidak dijelaskan apa arti tepatnya itu. Saya melakukan riset, dan tampaknya mengangkat ada hubungannya dengan nilai-nilai fungsional atau sesuatu seperti itu, tetapi saya tidak dapat menemukan teks yang menjelaskan tentang apa sebenarnya mengangkat itu dengan cara ramah pemula.

Ada kebingungan tambahan melalui kerangka kerja Lift yang mengangkat namanya, tetapi tidak membantu menjawab pertanyaan.

Apa itu "mengangkat" di Scala?

pengguna573215
sumber

Jawaban:

290

Ada beberapa penggunaan:

Fungsi Sebagian

Ingat a PartialFunction[A, B]adalah fungsi yang didefinisikan untuk beberapa subset domain A(sebagaimana ditentukan oleh isDefinedAtmetode). Anda bisa "mengangkat" PartialFunction[A, B]a Function[A, Option[B]]. Artinya, fungsi yang didefinisikan atas seluruh dari Anamun yang nilai adalah dari jenisOption[B]

Ini dilakukan oleh doa eksplisit metode liftpada 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)

Metode

Anda dapat "mengangkat" pemanggilan metode ke suatu fungsi. Ini disebut ekspansi eta (terima kasih kepada Ben James untuk ini). Jadi misalnya:

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

Kami mengangkat metode ke fungsi dengan menerapkan garis bawah

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

scala> f(4)
res0: Int = 8

Perhatikan perbedaan mendasar antara metode dan fungsi. res0adalah turunan (yaitu nilai ) dari tipe (fungsi)(Int => Int)

Functors

Sebuah functor (seperti yang didefinisikan oleh scalaz ) adalah beberapa "wadah" (Saya menggunakan istilah ini dengan sangat longgar), Fsehingga, jika kita memiliki F[A]dan fungsi A => B, maka kita bisa mendapatkan tangan kita F[B](pikirkan, misalnya, F = Listdan mapmetode )

Kami dapat menyandikan properti ini sebagai berikut:

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

Ini isomorfik untuk dapat "mengangkat" fungsi A => Bke domain functor. Itu adalah:

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

Artinya, jika Fadalah functor, dan kami memiliki fungsi A => B, kami memiliki fungsi F[A] => F[B]. Anda mungkin mencoba dan menerapkan liftmetode ini - ini cukup sepele.

Transformers Monad

Seperti yang dikatakan hcoopz di bawah ini (dan saya baru menyadari bahwa ini akan menyelamatkan saya dari menulis satu ton kode yang tidak perlu), istilah "lift" juga memiliki arti di dalam Monad Transformers . Ingatlah bahwa transformer monad adalah cara "menumpuk" monad di atas satu sama lain (monad tidak menulis).

Jadi misalnya, misalkan Anda memiliki fungsi yang mengembalikan sebuah IO[Stream[A]]. Ini dapat dikonversi ke trafo monad StreamT[IO, A]. Sekarang Anda mungkin ingin "mengangkat" beberapa nilai lain dan IO[B]mungkin juga merupakan StreamT. Anda bisa menulis ini:

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

Atau ini:

iob.liftM[StreamT]

ini menimbulkan pertanyaan: mengapa saya ingin mengubah sebuah IO[B]menjadi StreamT[IO, B]? . Jawabannya adalah "untuk mengambil keuntungan dari kemungkinan komposisi". Katakanlah Anda memiliki fungsif: (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
sumber
12
Mungkin perlu disebutkan bahwa "mengangkat metode ke suatu fungsi" sering disebut sebagai eta-ekspansi .
Ben James
7
Menggali lebih jauh ke scalaz , mengangkat juga muncul dalam kaitannya dengan trafo monad . Jika saya memiliki MonadTransinstance Tuntuk Mdan Monadinstance untuk N, maka T.liftMdapat digunakan untuk mengangkat nilai tipe N[A]ke nilai tipe M[N, A].
846846846
Terima kasih Ben, hcoopz. Saya telah memodifikasi jawaban
oxbow_lakes
Sempurna! Hanya satu alasan lagi untuk mengatakan: Scala - yang terbaik. Yang bisa diangkat ke Martin Odersky & Co - yang terbaik. Saya bahkan akan menggunakannya liftMuntuk itu, tetapi tidak berhasil mengerti bagaimana melakukannya dengan benar. Kawan, kau batu!
Dmitry Bespalov
3
Di bagian Metode ... res0 adalah sebuah instance (yaitu itu adalah nilai) dari tipe (function) (Int => Int) ... Bukankah fseharusnya sebuah instance, bukan res0?
srzhio
21

Penggunaan lain dari pengangkatan yang pernah saya temui di kertas (tidak harus terkait dengan Scala) adalah kelebihan fungsi dari f: A -> Bdengan f: List[A] -> List[B](atau set, multiset, ...). Ini sering digunakan untuk menyederhanakan formalisasi karena tidak masalah apakah fditerapkan pada elemen individu atau beberapa elemen.

Overloading semacam ini sering dilakukan secara deklaratif, misalnya,

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

atau

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

atau secara imperatif, misalnya,

f: List[A] -> List[B]
f(xs) = xs map f
Malte Schwerhoff
sumber
5
Ini adalah "mengangkat ke functor" yang dijelaskan oleh oxbow_lakes.
Ben James
7
@ BenJames Benar memang. Untuk pembelaan saya: jawaban oxbow_lakes 'belum ada ketika saya mulai menulis milik saya.
Malte Schwerhoff
20

Perhatikan setiap koleksi yang memanjang PartialFunction[Int, A](seperti yang ditunjukkan oleh oxbow_lakes) dapat diangkat; jadi misalnya

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

yang mengubah fungsi parsial menjadi fungsi total di mana nilai-nilai yang tidak didefinisikan dalam koleksi dipetakan None,

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

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

Bahkan,

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

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

Ini menunjukkan pendekatan yang rapi untuk menghindari pengecualian indeks .

elm
sumber
6

Ada juga unlifting , yang merupakan proses terbalik untuk mengangkat.

Jika mengangkat didefinisikan sebagai

mengubah fungsi parsial PartialFunction[A, B]menjadi fungsi totalA => Option[B]

maka unlifting adalah

mengubah fungsi total A => Option[B]menjadi fungsi parsial PartialFunction[A, B]

Perpustakaan standar Scala didefinisikan Function.unliftsebagai

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

Sebagai contoh, play-json library menyediakan unlift untuk membantu pembangunan serialis 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
sumber