1. ホーム
  2. function

[解決済み】Scalaの「リフティング」とは何ですか?

2022-04-02 15:32:42

質問

Scalaエコシステムの記事を読んでいると、時々 "lifting" / "lifed" という言葉を目にします。残念ながら、それが正確に何を意味するのかは説明されていません。調べてみると、lifting は関数値とかそういうものと関係があるようですが、実際に lifting がどういうものかを初心者にもわかりやすく説明している文章は見つかりませんでした。

を通じて、さらなる混乱が発生しています。 リフト フレームワークの名前にリフティングが含まれていますが、質問への回答にはなりません。

Scala における "lifting" とは何ですか?

どのように解決するのですか?

いくつかの使い方があります。

パーシャルファンクション

を覚えておいてください。 PartialFunction[A, B] は,ドメインの部分集合に対して定義された関数である. A (で指定される)。 isDefinedAt メソッド) を使用します。を "lift"することができます。 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)

ファンクター

A ファンクタ (で定義される)。 スカラ という用語を使っています。 極めて ということです。) 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]
}

このことは、関数「quot;lifting"」を使えることと同義である。 A => B をファンクタのドメインに入れる。つまり

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

つまり、もし F がファンクタであり、関数 A => B という関数があります。 F[A] => F[B] . を実装してみるのもいいかもしれません。 lift メソッドを使うのはとても簡単です。

モナドトランスフォーマー

として hcoopz という用語は、次のような意味を持っています(そうすれば、不必要なコードを書かずにすんだのに、と今気づきました)。 モナドトランスフォーマー . モナドトランスフォーマーはモナドを互いに"stacking"する方法であることを思い出してください(モナドは合成されません)。

例えば、ある関数があり、その関数が IO[Stream[A]] . これはモナド変換器に変換することができます StreamT[IO, A] . ここで、他の値を "lift"したい場合があります。 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]