때로는 스칼라 생태계에서 기사를 읽을 때 “리프팅”/ “리프팅”이라는 용어를 읽습니다. 불행히도, 그것이 정확히 무엇을 의미하는지는 설명되어 있지 않습니다. 나는 약간의 연구를했는데 리프팅은 기능적 가치 또는 이와 유사한 것과 관련이있는 것처럼 보이지만 실제로 리프팅이 초보자에게 친숙한 방법을 설명하는 텍스트를 찾을 수 없었습니다.
Lift 프레임 워크를 통해 이름이 바뀌는 추가 혼란이 있지만 질문에 대답하는 데 도움이되지 않습니다.
스칼라에서 “리프팅”이란 무엇입니까?
답변
몇 가지 사용법이 있습니다.
부분 기능
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 = List
과 map
방법 )
이 속성을 다음과 같이 인코딩 할 수 있습니다.
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]
즉, F
functor 인 경우 함수 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]
답변
논문 (스칼라 관련 문서는 아니지만)에서 들어 본 리프팅의 또 다른 사용법은 f: A -> B
with 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
답변
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
이것은 범위를 벗어난 인덱스 예외 를 피하는 깔끔한 접근 방식을 보여줍니다 .
답변
도있다 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))