命令型言語に慣れた方は、関数型言語を難しいと感じるようです。どこを難しいと感じるのかは人それぞれですが、関数をファーストオブジェクトと考えるところが一つの壁になっているようです。そこで今回は、関数型言語に慣れていないC#使いのために、高階関数の解説をします。
高階関数を簡潔に言うと、
関数を引数として受け取る関数や、関数を返す関数の事です。文章では分かり難いと思いますので、サンプルを示します。
using System;
using System.Collections.Generic;
class Higher_order_Function
{
static void Main( string[ ] args )
{
//データを準備する
Console.Write( "演算前の数値:" );
List< int > list = new List< int >();
for ( int i = 0 ; i < 10; i++) {
list.Add(i);
Console.Write( "{0} ", list[ i ] );
}
Console.WriteLine();
//加算演算を適用して表示
int value = 5;
Console.WriteLine( "{0}を加算", value );
Apply( list, ( x, y ) => x + y, value );
Console.Write( "加算後の値:" );
foreach( int val in list ) {
Console.Write( "{0} ", val );
}
Console.WriteLine( "\n" );
}
//数値リストに対して演算を適用する高階関数
static void Apply( List< int > list ,
Func< int, int, int > fun ,
int value )
{
for ( int i = 0; i < list.Count; i++ ) {
list[ i ] = fun( list[ i ], value );
}
}
}
このサンプルは、関数を引数として受け取る関数Applyを使用して、任意の演算を数値リストに対して適用しています。何の演算でも構わないのですが、このサンプルでは、整数値リストの全要素に対して加算演算を行っています。
サンプルコードの緑色の部分に注目して下さい。高階関数のポイントがつかめます。
.NETで関数を引数として受け取る関数を作成するには、デリゲートを使用します。呼び出す側は、ラムダ関数を使用します。ラムダ関数の使用は必須事項ではありません。複雑な関数を渡したい場合は、メソッドのポインターを指定しましょう。例えば、このサンプルでメソッドのポインターを指定する場合、加算を行う関数Addを定義してから 、 Apply( list, ( x, y ) => x + y, value );の部分をApply( list, Add, value );に変えます。
高階関数を使用することのメリットは、
定義する関数の数を減らし、アルゴリズムをより抽象的に捉える事が出来る事です。先ほどのサンプルを例に挙げると、これが良く分かると思います。もし高階関数を使用しなければ、加算・減算・乗算・除算・・・などの単純な関数を、何度もコーディングする作業が生じます。この作業は煩雑なだけで、何も良い事がありません。うっかりミスをしてしまうかもしれませんし、保守の負担が多くなります。また、仕様変更の際、変更漏れが生じるかもしれません。それにこの様な作業は、アルゴリズムの本質とは関係のない事です。
なお、今回のサンプルでは、単純化する為に型を限定しましたが、実務ではジェネリックを併用しましょう。ジェネリックを併用する事により、さらに多くの場面で使用できる関数が定義できます。
//ジェネリックな高階関数(※オーバーフローとアンダーフローに注意)
static void Apply< T >( List< T > list, Func< T, T, T > fun, T value )
{
for ( int i = 0; i < list.Count; i++ ) {
list[ i ] = fun( list[ i ], value );
}
}
今回の記事を通じて、高階関数が意外と難しくない事が分かって頂けたと思います。利点が多く、簡単な高階関数を使わない手はありません。どんどん使用しましょう。生産性が確実にUPします。ただし、使い過ぎには注意して下さい。たとえ有益な機能であっても、使い過ぎは毒になります。
テーマ : プログラミング
ジャンル : コンピュータ