Scala でリフティング

Suraj P 2023年1月30日
  1. メソッドを関数に変換する
  2. Scala で純粋な関数を Functor に変換する
  3. Scala で部分関数を関数に変換する
  4. Scala のモナドトランスフォーマー
  5. まとめ
Scala でリフティング

この記事では、Scala プログラミング言語でのリフティングについて説明します。リフティングにはさまざまな意味があります。それは私たちがそれを使用する文脈に依存します。

それらを一つずつ見ていきましょう。

メソッドを関数に変換する

メソッドを関数に変換したいという状況に遭遇することがあります。これはリフティングの一例です。

たとえば、次のメソッドがあるとします。

def mul(x: Int) = x*5
def isEven(x: Int) = x%2==0

これで、これらのメソッドを次のように構成できます。

isEven(mul(4))

しかし、この構成を機能的な方法で実行したい場合はどうでしょうか。次に、これらの上記のメソッドを関数に変換する必要があります。

これは、Scala の _ 記号を使用して持ち上げることで実行できます。

val mulAns = mul _
val isEvenAns = isEven _

これで、次のように機能的に構成できます。

( mulAns andThen isEvenAns)(5)

完全に機能するコード:

object MyClass {
    def mul(x: Int) = x*5
    def isEven(x: Int) = x%2==0

    def main(args: Array[String])
    {
        println(isEven(mul(4)));

        val mulAns = mul _
        val isEvenAns = isEven _

        println(( mulAns andThen isEvenAns)(5))
    }
}

出力:

true
false

説明:

最初の出力は true です。4 に 5 を掛けると、20 になります。この 20 は、パラメーターとして isEven 関数に渡されます。

2 番目の出力は false です。最初に 5 に 5 を掛けてから、その結果を isEven 関数でチェックします。

Scala で純粋な関数を Functor に変換する

Functor は、エンティティまたはオブジェクトの関数のように、カテゴリ間のマッピングを指します。たとえば、カテゴリ AB と、A's オブジェクトを B's オブジェクトにマップするファンクタ F があります。

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

    def lifting[A, B](f: A => B): F[A] => F[B] =
        X => map(X)(f)
}

この文脈では、リフティングとは、A=>B から関数を取得し、それを F[A] => F[B] の形式の関数にすることによって関数に変換することを指します。ネストされたデータタイプを操作する場合、これは非常に便利です。

Scala で部分関数を関数に変換する

このコンテキストでは、リフティングはドメインの拡張を指します。部分関数は、値のサブドメインに適用できる関数の一種です。

しかし、時にはその領域を拡張したくなることがある。それを実現するのがリフティングである。

正の数の平方根を与える部分関数があるとしましょう。

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

これで、lift メソッドを使用して部分関数のドメインを拡張し、よりエレガントに見せることができます。

def getSqrtRootPartialFunction(t: Double) =
{
    sqrt.lift(t).map(ans => s"Square root of ${t} is ${ans}")
        .getOrElse(s"Cannot calculate square root for t")
}

完全に機能するコード:

object MyClass {
     val sqrt: PartialFunction[Double, Double] = {
        case temp if temp >= 0 => Math.sqrt(temp)
    }

    def getSqrtRootPartialFunction(t: Double) = {
        sqrt.lift(t).map(ans => println(s"Square root of ${t} is ${ans}"))
           .getOrElse(println(s"Cannot calculate square root for t"))
    }

    def main(args: Array[String])
    {
        getSqrtRootPartialFunction(35)
    }
}

出力:

Square root of 35.0 is 5.916079783099616

説明:

したがって、ここで sqrt 関数を解除すると、ドメインが PartialFunction[Double, Double] から Function[Double, Option[Double]] に完全に拡張されます。

時々、部分関数を持ち上げることは、範囲外のインデックス例外を回避するために使用されます。

Seq("1", "2", "3").lift(1) // Some("2")

Seq("1", "2", "3").lift(7) // none returned

ドメインが String から Option[String] に拡張されました。したがって、範囲外の値にアクセスしようとすると、解除された Seq は、範囲外の例外をスローする代わりに、None を返します。

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

モナドを組み合わせるには、モナドトランスフォーマーを使用します。2つの Futures があるとしましょう。

val helloWorld: Future[Option[String]] = Future.successful(Some("hello world"))
val language: Future[String] = Future.successful("this is Scala language")

現在、両方の Futures は異なるドメインを持っているため、languageOptionT に持ち上げることでモナドトランスフォーマーを使用できます。これにより、より一貫した方法で結果を処理できます。

def MonadTransformer() = {
    val message: OptionT[Future, String] = for {
        hello <- OptionT(helloWorld)
        name  <- OptionT.liftF(language)
    } yield println(s"$hello $name")

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

    Await.result(result, 1 second)
}

まとめ

この記事では、リフティングの概念と、さまざまなコンテキストでのリフティングの意味を学びました。コードをより構成可能で慣用的な方法で作成すると、コードのさまざまな部分を構築するだけで時間を無駄にするのではなく、コードのビジネスロジックに集中できるようになるため、非常に便利です。

著者: Suraj P
Suraj P avatar Suraj P avatar

A technophile and a Big Data developer by passion. Loves developing advance C++ and Java applications in free time works as SME at Chegg where I help students with there doubts and assignments in the field of Computer Science.

LinkedIn GitHub

関連記事 - Scala Function