Що таке "підйом" у Scala?


252

Іноді, читаючи статті в екосистемі Scala, я читаю термін "підняти" / "підняти". На жаль, не пояснено, що саме це означає. Я провів деякі дослідження, і, схоже, підйом має щось спільне з функціональними значеннями чи чимось подібним, але мені не вдалося знайти текст, який би пояснював, що таке підйом насправді іде для початківців дружнім способом.

Існує додаткова плутанина через систему Lift, яка має назву, але це не допомагає відповісти на питання.

Що таке "підйом" у Scala?

Відповіді:


290

Є кілька звичаїв:

Частковефункція

Запам'ятати a PartialFunction[A, B]- це функція, визначена для деякого підмножини домену A(як визначено isDefinedAtметодом). Ви можете "підняти" a PartialFunction[A, B]в a Function[A, Option[B]]. Тобто, функція , певна над усіма з , Aале значення яких мають типуOption[B]

Це робиться шляхом явного виклику методу liftна 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)

Методи

Ви можете "підняти" виклик методу у функцію. Це називається ета-розширення (за це дякую Бену Джеймсу). Так, наприклад:

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]

12
Можливо, варто згадати, що «підняття методу до функції» часто називають ета-розширенням .
Бен Джеймс

7
Поглибившись далі у скалаз , підйом також виникає стосовно трансформаторів монади . Якщо у мене є MonadTransекземпляр Tдля Mі Monadекземпляр для N, то T.liftMйого можна використовувати для підняття значення типу N[A]до значення типу M[N, A].
846846846

Дякую Бен, hcoopz. Я змінив відповідь
oxbow_lakes

Ідеально! Ще лише одна причина сказати: Скала - найкраща. Яку можна було б віднести до Martin Odersky & Co - найкращого. Я навіть хотів би використати liftMдля цього, але не встиг зрозуміти, як це зробити правильно. Хлопці, ви рок!
Дмитро Беспалов

3
У розділі Методи ... res0 - це екземпляр (тобто це значення) типу (функція) (Int => Int) ... Не повинен fбути екземпляр, чи не так res0?
srzhio

21

Ще одне використання підйомів, які я зустрічав у документах (не обов'язково пов'язаних зі Скалою), - це перевантаження функції 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.
Бен Джеймс

6
@BenJames Справді. На мій захист: відповіді oxbow_lakes ще не було, коли я почав писати свою.
Malte Schwerhoff

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 дає змогу допомогти у створенні серіалізаторів 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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.