1. 序章

Scalaでのリフティングとは、適用される場所に応じて、さまざまなケースでさまざまなことを指します。 一般的な定義はないことに注意することが重要です。

この記事では、多くの場合に持ち上げることの意味と使用法を見て、それがもたらす利点を探ります。

2. 部分関数から関数へ

部分関数は、値のサブドメインに適用できる関数です。 ただし、ドメインを拡張したい場合があります。 この場合、リフティングにより、関数の定義域を拡張できます。

正の平方根を出力する関数を書いてみましょうダブル。 この部分関数を使用して、次のことから始めます。

val squareRoot: PartialFunction[Double, Double] = {
  case x if x >= 0 => Math.sqrt(x)
}

関数の定義域を変更しないでおくと、平方根を計算する値に対して関数が定義されているかどうかを確認する必要があります。

def getSqrtRootMessagePartialFunction(x: Double) = {
  if (squareRoot.isDefinedAt(x)) {
    s"Square root of $x is ${squareRoot(x)}"
  } else {
    s"Cannot calculate square root for $x"
  }
}

lift メソッドを使用して部分関数の定義域を拡張することにより、より慣用的な方法で同じコードを記述できます。

def getSqrtRootMessageTotalFunction(x: Double) = {
  squareRoot.lift(x).map(result => s"Square root of ${x} is ${result}")
    .getOrElse(s"Cannot calculate square root for $x")
}

squareRoot 関数を解除すると、その定義域がDouble全体に拡張されます。 PartialFunction [Double、Double] から、 Function [Double、Option[Double]]になります。

部分関数へのリフティングのもう1つの便利なアプリケーションは、「インデックスが範囲外」の例外を回避することです。

Seq("one", "two", "three").lift(1) // Some("two")

Seq("one", "two", "three").lift(7) // None

解除されたSeqドメインは Option[文字列]。 範囲外のインデックスに対応する値にアクセスすると、リフトされたSeqは例外ではなくNoneを返します。

3. 関数へのメソッド

メソッドを関数に変換する簡単な方法があると便利な場合があります。 これは持ち上げの別のケースです。 たとえば、次のメソッドがある場合:

def add5(x: Int) = x + 5
def isEven(x: Int) = x % 2 == 0

次のように2つの方法を構成できます。

isEven(add5(3))

ただし、それらを機能的に構成したい場合は、それらを関数に変換する方法が必要です。 Scalaでは、_シンボルを使用してそれらを持ち上げることができます。

val funcAdd5 = add5 _
val funcIsEven = isEven _

これで、より機能的な方法でそれらを簡単に作成できます。

(funcAdd5 andThen funcIsEven)(3)

4. 純粋関数から効果的な関数へ:ファンクター s

ファンクター[F]、では、 Cats ドキュメントで定義されているように、効果 F により、純粋関数を持ち上げることができます。

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]

  def lift[A, B](f: A => B): F[A] => F[B] =
    fa => map(fa)(f)
}

A => B から関数を取得し、それを Functor コンテキストに入れて、 F [A] =>F[B]の関数に変換する操作。 、はリフティングと呼ばれます。 ネストされたデータ型を操作するときに、Functor構成を活用できます。

List [Option[String]]の要素の長さを計算する関数を書いてみましょう。

def listOptionLength(l: List[Option[String]]): List[Option[Int]] =
  Functor[List].compose[Option].map(l)(_.length)

Functor コンポジションを使用すると、ネストされたさまざまなデータ型をアンラップする必要なしに、簡単にそれを実現できます。

5. モナドからモナド変換子

モナド変換子は、モナドを簡単に組み合わせる方法です。 リフティングの概念がモナド変換子にどのように適用されるかを見てみましょう。

まず、2つの Future s があるとします。

val sayHello: Future[Option[String]] = Future.successful(Some("Say hello to"))
val firstname: Future[String] = Future.successful("Fabio")

私たちが扱っている2つのFutureには、2つの異なるドメインがあります。 これは、結果を処理するときに、一貫性のない方法で結果を処理する必要があることを意味します。

def getGreetingsBasic() = {
  val maybeHello: Future[String] = for {
    hello <- sayHello
    name  <- firstname
  // $hello is an Option and need to be unwrapped to get the result
  // $name is already a String
  } yield s"${hello.get} $name"

  Await.result(maybeHello, 1 second)
}

モナド変換子を使用して、 firstNameOptionT、に持ち上げるときに、Futureをより一貫した方法で処理できます。

def getGreetingsMonadTranformer() = {
  val maybeHello: OptionT[Future, String] = for {
    hello <- OptionT(sayHello)
    name  <- OptionT.liftF(firstname)
  } yield s"$hello $name"

  val result: Future[Option[String]] = maybeHello.value

  Await.result(result, 1 second)
}

6. 結論

このチュートリアルでは、リフティングの概念がドメインの変換を意味することを確認しました。 ボイラープレートコードの一部を削除し、より慣用的で構成可能な方法でコードを記述することは非常に役立ちます。 これにより、コードを構築するさまざまな部分の配管ではなく、コードのビジネスロジックに集中できます。

この記事の完全なソースコードは、GitHubから入手できます。