web-dev-qa-db-ja.com

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

Scalaエコシステムで記事を読むとき、「リフティング」/「リフテッド」という用語を読みます。残念ながら、それが正確に何を意味するのかは説明されていません。私はいくつかの研究を行いましたが、リフティングは機能的価値などと関係があるようですが、リフティングが実際には初心者に優しい方法で説明しているテキストを見つけることができませんでした。

Lift フレームワークには混乱がありますが、このフレームワークには名前が持ち上がっていますが、質問に答える助けにはなりません。

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

241
user573215

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

部分関数

PartialFunction[A, B]は、ドメインAのサブセット(isDefinedAtメソッドで指定)に対して定義された関数であることを忘れないでください。 PartialFunction[A, B]を「持ち上げて」Function[A, Option[B]]にすることができます。つまり、Awholeで定義されているが、値が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

メソッドと関数の基本的な違いに注意してください。 res0instance(つまり、(関数)タイプ(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]
278
oxbow_lakes

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
21

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例外を回避するためのきちんとしたアプローチを示しています。

19
elm

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))
6
Mario Galic