스칼라에서 "리프팅"이란 무엇입니까?


252

때로는 스칼라 생태계에서 기사를 읽을 때 "리프팅"/ "리프팅"이라는 용어를 읽습니다. 불행히도, 그것이 정확히 무엇을 의미하는지는 설명되어 있지 않습니다. 나는 약간의 연구를했는데 리프팅은 기능적 가치 또는 이와 유사한 것과 관련이있는 것처럼 보이지만 실제로 리프팅이 초보자에게 친숙한 방법을 설명하는 텍스트를 찾을 수 없었습니다.

Lift 프레임 워크를 통해 이름이 바뀌는 추가 혼란이 있지만 질문에 대답하는 데 도움이되지 않습니다.

스칼라에서 "리프팅"이란 무엇입니까?

답변:


290

몇 가지 사용법이 있습니다.

부분 기능

a PartialFunction[A, B]는 도메인의 일부 하위 집합에 대해 정의 된 함수입니다 A( isDefinedAt메소드에서 지정한대로 ). a를 PartialFunction[A, B]로 들어 올릴 수 있습니다 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 = Listmap방법 )

이 속성을 다음과 같이 인코딩 할 수 있습니다.

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

이것은 기능 A => B을 functor의 영역으로 "리프팅"할 수있는 동형 입니다. 그건:

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

즉, Ffunctor 인 경우 함수 A => B가 있고 함수가 F[A] => F[B]있습니다. lift메소드를 시도하고 구현할 수 있습니다 -그것은 사소한 것입니다.

모나드 트랜스포머

으로 hcoopz가 (난 그냥이 불필요한 코드의 톤을 쓰는 저를 저장 한 것을 깨달았다) 아래 말한다, 용어 "리프트"도 내에서 의미가 모나드 변압기 . 모나드 변압기는 모나드를 서로 쌓아 올리는 방법입니다 (모나드는 작성하지 않음).

예를 들어,를 반환하는 함수가 있다고 가정합니다 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
"함수로 방법을 들어 올리는 것"을 종종 eta-expansion 이라고합니다 .
벤 제임스

7
scalaz 로 더 나아가 , 모나드 변압기 와 관련하여 리프팅 도 발생합니다 . 나는이있는 경우 예를 위한 과 에 대한 인스턴스를 , 다음 할 수 있습니다 들어 올려 형의 값을 유형의 값 . MonadTransTMMonadNT.liftMN[A]M[N, A]
846846846

고마워 벤. 답변을 수정했습니다
oxbow_lakes

완전한! 한 가지 더 말할 이유 : 스칼라 – 최고. Martin Odersky & Co로 들어 올릴 수있는 것이 가장 좋습니다. 나는 심지어 그것을 위해 사용 liftM하고 싶지만 제대로 관리하는 방법을 이해하지 못했습니다. 여러분, 당신은 바위입니다!
Dmitry Bespalov

3
에서는 방법...이 res0 인스턴스가 (기능) 타입 (INT => INT)의 (즉, 그것의 값이다)이다 ... 한다 안 f, 인스턴스가 아닌 수 res0?
srzhio

21

논문 (스칼라 관련 문서는 아니지만)에서 들어리프팅의 또 다른 사용법은 f: A -> Bwith 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가 설명하는 "functor로 들어 올리기"입니다.
벤 제임스

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

도있다 unlifting 리프팅의 역 과정이다.

리프팅이 다음과 같이 정의 된 경우

부분 함수 PartialFunction[A, B]를 전체 함수로 바꾸기A => Option[B]

그런 다음 해제

전체 함수 A => Option[B]를 부분 함수로 바꾸기 PartialFunction[A, B]

스칼라 표준 라이브러리를 정의 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))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.