Scalaの「リフティング」とは何ですか?


253

Scalaエコシステムの記事を読むとき、「リフティング」/「リフティング」という用語を読むことがあります。残念ながら、その正確な意味は説明されていません。調べてみたところ、リフティングは関数の値などに関係しているようですが、リフティングが初心者にやさしい方法で実際に説明しているテキストは見つかりませんでした。

その名前にリフティングがあるLiftフレームワークを介してさらに混乱がありますが、それは質問に答える助けにはなりません。

Scalaの「リフティング」とは何ですか?

回答:


290

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

PartialFunction

a PartialFunction[A, B]はドメインの一部のサブセットに対して定義された関数であることを覚えておいてくださいAisDefinedAtメソッドで指定されているとおり)。あなたは缶「リフト」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)

方法

メソッド呼び出しを関数に「リフト」できます。これはeta-expansionと呼ばれます(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は(と私は、これは不要なコードのトンを書いてから私を救っていたことに気づきました)以下と言い、用語「リフト」も内部の意味があるモナド変圧器を。モナド変換子は、モナドを互いに「積み重ねる」方法であることを思い出してください(モナドは構成しません)。

たとえば、を返す関数があるとします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
スカラズをさらに掘り下げるとモナド変換器に関連してリフティングも発生します。私が持っている場合は、インスタンスのためにとのインスタンスを、その後に使用することができるリフトタイプの値を型の値に。MonadTransTMMonadNT.liftMN[A]M[N, A]
846846846 2013

ありがとう、ベン、hcoopz。私は答えを変更しました
oxbow_lakes 2013

パーフェクト!もう1つ言う理由は、Scala-最高です。Martin Odersky&Coにとっては最高の方法です。私もliftMそのために使用したいのですが、それを適切に行う方法を理解できませんでした。みんな、あなたはロックです!
ドミトリーベスパロフ2015

3
ではメソッドセクション... RES0は、インスタンスは、(機能)タイプ(のInt => INT)の(すなわち、それは価値がある)である...べきではないf、インスタンスではありませんことres0
srzhio 2018年

21

私がペーパーで遭遇したリフティングのもう1つの使用法(必ずしもScala関連のものではない)は、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が説明する「ファンクタへの持ち上げ」です。
ベンジェームズ

6
@BenJames確かにそうだ。私の弁護のために:私が私のものを書き始めたとき、oxbow_lakesの答えはまだありませんでした。
Malte Schwerhoff 2013

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]

Scala標準ライブラリは次の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.