Scalaエコシステムの記事を読むとき、「リフティング」/「リフティング」という用語を読むことがあります。残念ながら、その正確な意味は説明されていません。調べてみたところ、リフティングは関数の値などに関係しているようですが、リフティングが初心者にやさしい方法で実際に説明しているテキストは見つかりませんでした。
その名前にリフティングがあるLiftフレームワークを介してさらに混乱がありますが、それは質問に答える助けにはなりません。
Scalaの「リフティング」とは何ですか?
Scalaエコシステムの記事を読むとき、「リフティング」/「リフティング」という用語を読むことがあります。残念ながら、その正確な意味は説明されていません。調べてみたところ、リフティングは関数の値などに関係しているようですが、リフティングが初心者にやさしい方法で実際に説明しているテキストは見つかりませんでした。
その名前にリフティングがあるLiftフレームワークを介してさらに混乱がありますが、それは質問に答える助けにはなりません。
Scalaの「リフティング」とは何ですか?
回答:
いくつかの使い方があります:
a PartialFunction[A, B]
はドメインの一部のサブセットに対して定義された関数であることを覚えておいてくださいA
(isDefinedAt
メソッドで指定されているとおり)。あなたは缶「リフト」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]
MonadTrans
T
M
Monad
N
T.liftM
N[A]
M[N, A]
liftM
そのために使用したいのですが、それを適切に行う方法を理解できませんでした。みんな、あなたはロックです!
f
、インスタンスではありませんことres0
?
私がペーパーで遭遇したリフティングのもう1つの使用法(必ずしもScala関連のものではない)は、f: A -> B
with 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
これは、範囲外のインデックスの例外を回避するための適切なアプローチを示しています。
また、持ち上げの逆のプロセスである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))