関数型プログラミングのコンセプト

関数型プログラミングの概要

Earth Engine は、並列処理システムを使用して、多数のマシンで計算を実行します。このような処理を可能にするため、Earth Engine は、参照透過性や遅延評価など、関数型言語で一般的に使用される標準的な手法を利用して、大幅な最適化と効率化を実現しています。

関数型プログラミングと手続き型プログラミングを区別する主なコンセプトは、副作用がないことです。つまり、作成する関数は、関数の外部のデータに依存したり、更新したりしません。以下の例で示すように、副作用のない関数を使用して解決できるように問題を再構築することが可能です。これは、並列実行に非常に適しています。

For ループ

Earth Engine では、for ループの使用は推奨されていません。同じ結果を得るには、map() オペレーションを使用して、各要素に個別に適用できる関数を指定します。これにより、システムは処理を異なるマシンに分散できます。

以下の例は、数値のリストを取得し、map() を使用して各数値の 2 乗を含む別のリストを作成する方法を示しています。

コードエディタ(JavaScript)

// This generates a list of numbers from 1 to 10.
var myList = ee.List.sequence(1, 10);

// The map() operation takes a function that works on each element independently
// and returns a value. You define a function that can be applied to the input.
var computeSquares = function(number) {
  // We define the operation using the EE API.
  return ee.Number(number).pow(2);
};

// Apply your function to each item in the list by using the map() function.
var squares = myList.map(computeSquares);
print(squares);  // [1, 4, 9, 16, 25, 36, 49, 64, 81]

If/Else 条件

手続き型プログラミング パラダイムに慣れている新規ユーザーが直面するもう 1 つの一般的な問題は、Earth Engine での if/else 条件演算子の適切な使用です。API には ee.Algorithms.If() アルゴリズムが用意されていますが、map() とフィルタを使用したより機能的なアプローチを優先するため、このアルゴリズムの使用は強く非推奨とされています。Earth Engine は 遅延実行を使用します。つまり、式の評価は、実現された値が実際に必要になるまで遅延されます。場合によっては、このタイプの実行モデルは、ee.Algorithms.If() ステートメントの true と false の両方の代替を評価します。これにより、式と、式の実行に必要なリソースによっては、追加の計算とメモリ使用量が発生する可能性があります。

上記の例のバリエーションとして、奇数の二乗のみを計算するタスクを解く場合を考えてみましょう。if/else 条件を使用せずにこの問題を解決する関数型のアプローチを以下に示します。

コードエディタ(JavaScript)

// The following function determines if a number is even or odd.  The mod(2)
// function returns 0 if the number is even and 1 if it is odd (the remainder
// after dividing by 2).  The input is multiplied by this remainder so even
// numbers get set to 0 and odd numbers are left unchanged.
var getOddNumbers = function(number) {
  number = ee.Number(number);   // Cast the input to a Number so we can use mod.
  var remainder = number.mod(2);
  return number.multiply(remainder);
};

var newList = myList.map(getOddNumbers);

// Remove the 0 values.
var oddNumbers = newList.removeAll([0]);

var squares = oddNumbers.map(computeSquares);
print(squares);  // [1, 9, 25, 49, 81]

このパラダイムは、コレクションを扱う場合に特に有効です。なんらかの条件に基づいてコレクションに別のアルゴリズムを適用する場合は、まず条件に基づいてコレクションをフィルタし、次に各サブセットに別の関数を map() するのが望ましい方法です。これにより、システムはオペレーションを並列化できます。次に例を示します。

コードエディタ(JavaScript)

// Import Landsat 8 TOA collection and filter to 2018 images.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate('2018-01-01', '2019-01-01');

// Divide the collection into 2 subsets and apply a different algorithm on them.
var subset1 = collection.filter(ee.Filter.lt('SUN_ELEVATION', 40));
var subset2 = collection.filter(ee.Filter.gte('SUN_ELEVATION', 40));

// Multiply all images in subset1 collection by 2;
// do nothing to subset2 collection.
var processed1 = subset1.map(function(image) {
  return image.multiply(2);
});
var processed2 = subset2;

// Merge the collections to get a single collection.
var final = processed1.merge(processed2);
print('Original collection size', collection.size());
print('Processed collection size', final.size());

累積イテレーション

各イテレーションの結果が後続のイテレーションで使用される、順次オペレーションが必要になる場合があります。Earth Engine には、このようなタスク用の iterate() メソッドが用意されています。iterate() は順次実行されるため、大規模なオペレーションでは処理が遅くなります。map() とフィルタを使用して目的の出力を得ることができない場合にのみ使用してください。

iterate() の良いデモは、フィボナッチ数シーケンスの作成です。この数列では、各数字は前の 2 つの数字の合計です。iterate() 関数は、関数(アルゴリズム)と開始値の 2 つの引数を取ります。関数自体には、反復処理の現在の値と、前の反復処理の結果の 2 つの値が渡されます。次の例は、Earth Engine でフィボナッチ数列を実装する方法を示しています。

コードエディタ(JavaScript)

var algorithm = function(current, previous) {
  previous = ee.List(previous);
  var n1 = ee.Number(previous.get(-1));
  var n2 = ee.Number(previous.get(-2));
  return previous.add(n1.add(n2));
};

// Compute 10 iterations.
var numIteration = ee.List.repeat(1, 10);
var start = [0, 1];
var sequence = numIteration.iterate(algorithm, start);
print(sequence);  // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

JavaScript のコンセプトを十分に理解したら、API チュートリアルで Earth Engine API の地理空間機能の概要を確認してください。