1. 序章

関数の合成は、複数の関数を1つ以上の値に次々に適用することと考えることができます。

このチュートリアルでは、Scalaで関数合成を使用する2つの方法を見ていきます。 構成の理論的な定義から始めて、それを実装する方法を見ていきます。

2. 関数の合成とは

Scalaで関数の合成に飛び込む前に、数学を少し見て、関数の合成を定義しましょう。

2つの関数f:X-> Yおよびg:Y-> Zが与えられた場合、それらの構成を関数h =g∘f:X-> Zとして定義できます。ここで、h(x)= g(f( x))。つまり、 h は、終域Z内のドメインXの要素をマップする関数です。 f は、Xの要素をgのドメインであるYの1つにマップするのに必要です。 次に、gを使用してZの要素を取得します。

表記を読む方法はたくさんあります g ∘f。 最も一般的なのは「gはfで構成されている」と「ftheng」です。

関数の合成にはいくつかの優れた特性があります。 つまり、関数の合成は常に結合法則です:f∘(g∘h)=(f∘g)∘h。

一方、可換性は、特定の関数によってのみ、多くの場合は特別な状況で達成される特性です。たとえば、 f(x)= x ^2およびg (x)= x ^ 3 は、 f(g(x))= g(f(x))= x ^6として交換します。 一方、 f(x)= x + 3およびg(x)= | x | x の絶対値)は、xの場合にのみ通勤します。 X114X] x> =0。

3. Scalaで関数を構成する

Scalaでは、トレイトFunction1 [T1、R]は、関数を構成するメソッドを定義します。 Function1 は、単項関数、つまり単一のパラメーターを持つ関数( T1 trait 定義)で、タイプRの値を生成します。

Function1:composeとandThenによると、このような関数を作成する方法は2つあります。 2つの違いは、アプリケーションの順序に依存します。 2つの単項関数fgが与えられると、fと次にgが最初にf を適用し、次にgを適用します。 逆に、f compose g は、最初に g を適用し、次にfを適用します。

3.1. そして

Function1 [T1、R]andThenをどのように定義するかを見てみましょう。

def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) }

andThen は、 Function1 [R、A] を入力します。これは、Rから新しいタイプAへの関数です。 結果は、T1からAまでの関数です。つまり、 f:Function1 [T1、R]およびg:Function1 [R 、 A]、 f.andThen(g)は、 f T1、および g Aの終域。

例を見てみましょう:

val f = (x: Float) => x.abs
val g = (x: Float) => x + 3
val h1 = f andThen g
val h2 = g andThen f

assert(h1(-1) == 4f)
assert(h2(-1) == 2f)

上記の例は、2つの関数が異なる順序で適用されることを示しています。 h1(-1)は、 g(f(-1))と同等であり、4と同等です。 一方、 h2(-1)は、 f(g(-1))と同等であり、2と同じです。

3.2. 作成

Function1 [T1、R]composeをどのように定義するかを見てみましょう。

def compose[A](g: A => T1): A => R = { x => apply(g(x)) }

composeは、新しいタイプAからT1への関数であるFunction1 [A、T1]を入力します。 結果はAからRまでの関数です。これは、f:Function1 [T1、R]およびg:Function1 [A、T1]が与えられた場合、f.compose(g)がg、A、の同じ定義域を持つ関数を生成することを意味します。そしてf、Rの終域。 言い換えれば、これはの反対ですその後

例を見てみましょう:

val f = (x: Float) => x.abs
val g = (x: Float) => x + 3
val h1 = f compose g
val h2 = g compose f

assert(h1(-1) == 2f)
assert(h2(-1) == 4f)

上記の例は、composeがandThenとは異なるアプリケーションの順序で動作することを示しています。 h1(-1) f(g(-1))[と同じになりました。 X153X]、これは2と同じです。 一方、 h2(-1)は、 g(f(-1))と同等であり、4を返します。

3.3. 複数のパラメーターを持つ関数の構成

これまで、単項関数に適用される関数合成を見てきました。 次に、この概念を、バイナリ関数(1つではなく2つのパラメーター)などの他のタイプの関数に拡張する方法を見てみましょう。

Scalaは、Function2 [T1、T2、R]トレイトを使用してバイナリ関数をモデル化します。ここで、T1とT2はパラメーターのタイプであり、Rは以前と同様に戻り値のタイプです。ただし、[X188X ] Function2 は、andThenまたはcomposeのいずれも定義していません。

それでも、Function2tupledという名前のメソッドを定義します。

def tupled: Tuple2[T1, T2] => R = {
  case Tuple2(x1, x2) => apply(x1, x2)
}

tupledは基本的に、タイプT1 => T2 => Rの関数をタイプ(T1、T2)=>Rの関数に変換します。 つまり、2つの別々の引数(1つはT1用、もう1つはT2用)を入力する代わりに、新しい関数は2つの要素のタプル(ScalaではTuple2)である1つの引数を入力します。 このようにして、バイナリ関数から単項関数を取得できます。 したがって、前に見たように、関数合成を簡単に使用できます。

val f = (x: Int, y: Int) => (x + 1, y + 2)
val g = (x: Int, y: Int) => x - y
val h = f.tupled andThen g.tupled

assert(h((5, 4)) == 0)

上記の例では、fはペアを返すバイナリ関数です。 f が2つの数値を返す場合でも、Scalaの観点からは、タイプ Tuple2 [Int、Int]の単一の値です。 g も二変数関数であるため、そのままでは作成できません。 代わりに、両方でtupledを呼び出す必要があります。 f.tupledg .tupledは次のように考えることができます。

val ftupl = (t: (Int, Int)) => (t._1 + 1, t._2 + 2)
val gtupl = (t: (Int, Int) ) => t._1 - t._2

タイプと本体は少し読みにくいですが、それぞれfgと同等です。 それでも、ftuplgtuplは単項関数なので、 andThen を呼び出して、hを定義できます。

単一のバイナリ関数(fまたはg)がある場合、同じメカニズムを適用できます。例を見てみましょう。

val f1 = (x: Int, y: Int) => x + y
val g1 = (x: Int) => x + 1
val h1 = f1.tupled andThen g1
assert(h1((5, 4)) == 10)

val f2 = (x: Int) => (x + 1, x - 1)
val g2 = (x: Int, y: Int) => x * y
val h2 = f2 andThen g2.tupled
assert(h2(2) == 3)

上記の例では、f1g2のみが二変数であり、g1f2は単項関数です。 この場合、引数が複数ある場合にのみtupledを適用できます。

4. 結論

このチュートリアルでは、Scalaで関数合成を使用する方法を見ました。 数学的な定義から始めて、Scalaの単項関数を表す Function1 を調べ、標準ライブラリがcomposeandThenをどのように定義し、それらを使用するかを確認しました。 。 また、概念を二変数に拡張しました。

いつものように、コードはGitHubにあります。