Scalaエコシステムで記事を読むとき、「リフティング」/「リフテッド」という用語を読みます。残念ながら、それが正確に何を意味するのかは説明されていません。私はいくつかの研究を行いましたが、リフティングは機能的価値などと関係があるようですが、リフティングが実際には初心者に優しい方法で説明しているテキストを見つけることができませんでした。
Lift フレームワークには混乱がありますが、このフレームワークには名前が持ち上がっていますが、質問に答える助けにはなりません。
Scalaの「リフティング」とは何ですか?
いくつかの使用法があります。
PartialFunction[A, B]
は、ドメインA
のサブセット(isDefinedAt
メソッドで指定)に対して定義された関数であることを忘れないでください。 PartialFunction[A, B]
を「持ち上げて」Function[A, Option[B]]
にすることができます。つまり、A
のwholeで定義されているが、値が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)
メソッド呼び出しを関数に「リフト」できます。これはeta-expansionと呼ばれます(これについてBen Jamesに感謝します)。たとえば、次のとおりです。
scala> def times2(i: Int) = i * 2
times2: (i: Int)Int
underscoreを適用して、メソッドを関数に持ち上げます
scala> val f = times2 _
f: Int => Int = <function1>
scala> f(4)
res0: Int = 8
メソッドと関数の基本的な違いに注意してください。 res0
はinstance(つまり、(関数)タイプ(Int => Int)
のvalue)です
A functor (as defined by scalaz) is some "container" (I use the term extremely loosely), F
such that, if we have an F[A]
and a function A => B
, then we can get our hands on an F[B]
(think, for example, F = List
and the map
method)
このプロパティは次のようにエンコードできます。
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]
liftingの別の使用法は、(必ずしもScala関連のものではなく)論文で出会ったことですが、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
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
これは、index out of bounds例外を回避するためのきちんとしたアプローチを示しています。
unliftingもあります。これは、リフティングの逆のプロセスです。
リフティングが次のように定義されている場合
部分関数
PartialFunction[A, B]
を合計関数A => Option[B]
に変換する
解除は
合計関数
A => Option[B]
を部分関数PartialFunction[A, B]
に変える
Scala標準ライブラリは Function.unlift
を定義します
def unlift[T, R](f: (T) ⇒ Option[R]): PartialFunction[T, R]
たとえば、play-jsonライブラリは nlift を提供して、 JSON serialisers の構築を支援します。
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))