サーチ…


構文

  • aFunction(10)_ // '_'を使用する残りのパラメータグループのすべてのパラメータがカリングされることをコンパイラに指示します。
  • nArityFunction.curried // n-arity関数を同等のカレー化バージョンに変換します
  • anotherFunction(x)(_:String)(z)//任意のパラメータをカリングします。それは明示的に述べられた型を必要とする。

カルト関数として設定可能な乗数

def multiply(factor: Int)(numberToBeMultiplied: Int): Int = factor * numberToBeMultiplied

val multiplyBy3 = multiply(3)_     // resulting function signature Int => Int
val multiplyBy10 = multiply(10)_ // resulting function signature Int => Int

val sixFromCurriedCall = multiplyBy3(2) //6
val sixFromFullCall = multiply(3)(2)    //6

val fortyFromCurriedCall = multiplyBy10(4) //40
val fortyFromFullCall = multiply(10)(4)    //40

異なるタイプの複数のパラメータグループ、任意の位置のカリングパラメータ

def numberOrCharacterSwitch(toggleNumber: Boolean)(number: Int)(character: Char): String = 
  if (toggleNumber) number.toString else character.toString

// need to explicitly specify the type of the parameter to be curried
// resulting function signature Boolean => String
val switchBetween3AndE = numberOrCharacterSwitch(_: Boolean)(3)('E') 

switchBetween3AndE(true) // "3"
switchBetween3AndE(false) // "E"

単一のパラメータグループで関数をカリングする

def minus(left: Int, right: Int) = left - right

val numberMinus5 = minus(_: Int, 5)
val fiveMinusNumber = minus(5, _: Int)

numberMinus5(7)    //  2
fiveMinusNumber(7) // -2

カッシング

2つの引数の関数を定義しましょう:

def add: (Int, Int) => Int = (x,y) => x + y
val three = add(1,2) 

Currying addは、 1つの Intをとり、 関数を1つの IntからInt )返す関数に変換します。

val addCurried: (Int) => (Int => Int) = add2.curried
//               ^~~ take *one* Int
//                        ^~~~ return a *function* from Int to Int

val add1: Int => Int = addCurried(1)
val three: Int = add1(2)
val allInOneGo: Int = addCurried(1)(2)

この概念は、複数の引数を取る関数に適用できます。複数の引数をとる関数をカリングすると、 1つの引数を取る関数の一連のアプリケーションに変換されます。

def add3: (Int, Int, Int) => Int = (a,b,c) => a + b + c + d
def add3Curr: Int => (Int => (Int => Int)) = add3.curried

val x = add3Curr(1)(2)(42)

カッシング

Wikipediaによる 、カリングは、

複数の引数を取る関数の評価を一連の関数の評価に変換する技法です。

具体的には、スカラー型の観点から、2つの引数を取る関数(文脈2がある)の文脈では、

val f: (A, B) => C // a function that takes two arguments of type `A` and `B` respectively 
                   // and returns a value of type `C`

val curriedF: A => B => C // a function that take an argument of type `A` 
                          // and returns *a function* 
                          // that takes an argument of type `B` and returns a `C`

したがって、arity-2関数の場合、カリー関数を次のように書くことができます。

def curry[A, B, C](f: (A, B) => C): A => B => C = { 
  (a: A) => (b: B) => f(a, b) 
}

使用法:

val f: (String, Int) => Double = {(_, _) => 1.0}
val curriedF: String => Int => Double = curry(f)
f("a", 1)        // => 1.0
curriedF("a")(1) // => 1.0

Scalaはこれに役立ついくつかの言語機能を提供しています:

  1. メソッドとしてカルト関数を書くことができます。 curriedFは次のように書くことができます:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. 標準的なライブラリーメソッドを使用してカレットを取り除くことができます(つまり、 A => B => Cから(A, B) => CFunction.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

カッティングを使用する場合

currying は、複数の引数をとる関数の評価を、それぞれが単一の引数を持つ関数のシーケンスの評価に変換する技法です

これは、通常、次のような場合に便利です。

  1. 関数の異なる引数は異なる時に計算されます(実施例1)
  2. 関数の異なる引数は、アプリケーションの異なる層によって計算されます(実施例2)

例1

総年間収入は収入とボーナスで構成された関数であるとしましょう:

val totalYearlyIncome:(Int,Int) => Int =  (income, bonus) => income + bonus

上記2-arity関数のカレー化バージョンは次のとおりです。

val totalYearlyIncomeCurried: Int => Int => Int = totalYearlyIncome.curried

上記の定義では、そのタイプは次のように表示/記述することができます。

Int => (Int => Int)

年収部分が事前に分かっていると仮定しましょう。

val partialTotalYearlyIncome: Int => Int = totalYearlyIncomeCurried(10000)

そして、ある点では、ボーナスは分かっています。

partialTotalYearlyIncome(100)

例2

車の製造には車輪と車体の適用が必要であると仮定しましょう。

val carManufacturing:(String,String) => String = (wheels, body) => wheels + body

これらの部分は、異なる工場で適用されます。

class CarWheelsFactory {
  def applyCarWheels(carManufacturing:(String,String) => String): String => String =
          carManufacturing.curried("applied wheels..")
}
    
class CarBodyFactory {
  def applyCarBody(partialCarWithWheels: String => String): String = partialCarWithWheels("applied car body..")
}

上のCarWheelsFactoryは車の製造機能をCarWheelsFactory 、車輪のみを適用することに注意してCarWheelsFactory

車の製造プロセスは、次の形式をとります:

val carWheelsFactory = new CarWheelsFactory()
val carBodyFactory   = new CarBodyFactory()

val carManufacturing:(String,String) => String = (wheels, body) => wheels + body
  
val partialCarWheelsApplied: String => String  = carWheelsFactory.applyCarWheels(carManufacturing)
val carCompleted = carBodyFactory.applyCarBody(partialCarWheelsApplied)

Curryingの現実の世界の使用。

私たちが持っているものは、クレジットカードのリストです。クレジットカード会社が支払うべきすべてのカードの保険料を計算したいと思います。保険料自体はクレジットカードの総数に依存するため、会社はそれに応じて調整します。

私たちはすでに単一のクレジットカードのプレミアムを計算し、会社が発行したカードの総数を考慮に入れた関数を持っています:

case class CreditCard(creditInfo: CreditCardInfo, issuer: Person, account: Account)

object CreditCard {
  def getPremium(totalCards: Int, creditCard: CreditCard): Double = { ... }
}

今、この問題に対する合理的なアプローチは、各クレジットカードをプレミアムにマッピングし、それを合計に減らすことです。このようなもの:

val creditCards: List[CreditCard] = getCreditCards()
val allPremiums = creditCards.map(CreditCard.getPremium).sum //type mismatch; found : (Int, CreditCard) ⇒ Double required: CreditCard ⇒ ?

しかし、 CreditCard.getPremiumは2つのパラメータを必要とするため、コンパイラはこれを気にしません。救助への部分的な適用!クレジットカードの総数を部分的に適用し、その機能を使用してクレジットカードを保険料にマッピングすることができます。必要なのは、 getPremium関数を複数のパラメータリストを使用するように変更してカレーするgetPremiumです。

結果は次のようになります。

object CreditCard {
  def getPremium(totalCards: Int)(creditCard: CreditCard): Double = { ... }
}

val creditCards: List[CreditCard] = getCreditCards()

val getPremiumWithTotal = CreditCard.getPremium(creditCards.length)_

val allPremiums = creditCards.map(getPremiumWithTotal).sum


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow