Scala エコシステムに関する記事を読んでいると、「リフティング」または「リフトされた」という用語を目にすることがあります。残念ながら、それが正確に何を意味するのかは説明されていません。調べてみたところ、リフティングは関数値などと関係があるようですが、初心者にわかりやすい方法でリフティングが実際に何であるかを説明するテキストを見つけることができませんでした。
名前に「lifting」が含まれているLiftフレームワークによってさらに混乱が生じますが、質問への回答には役立ちません。
Scala における「リフティング」とは何ですか?
ベストアンサー1
いくつかの使用法があります:
部分関数
は、メソッドで指定されたPartialFunction[A, B]
ドメインのサブセットに対して定義された関数であることに注意してください。 aを a に「持ち上げる」ことができます。つまり、の全体にわたって定義された関数ですが、その値はA
isDefinedAt
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)
方法
メソッド呼び出しを関数に「持ち上げる」ことができます。これはイータ展開と呼ばれます(これについては@Ben Jamesに感謝します)。たとえば、次のようになります。
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* が以下で述べているように (そして、これで大量の不要なコードを書かずに済んだことに今気づきました)、「lift」という用語は **モナド トランスフォーマー** 内でも意味を持ちます。モナド トランスフォーマーは、モナドを互いに「積み重ねる」方法であることを思い出してください (モナドは合成しません)。たとえば、 を返す関数があるとします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]