Koncepcje programowania funkcyjnego

Wprowadzenie do programowania funkcyjnego

Earth Engine używa systemu przetwarzania równoległego do wykonywania obliczeń na dużej liczbie maszyn. Aby umożliwić takie przetwarzanie, Earth Engine wykorzystuje standardowe techniki powszechnie używane przez języki funkcyjne, takie jak przejrzystość referencyjna i leniwa ewaluacja, co pozwala na znaczną optymalizację i zwiększenie wydajności.

Główną koncepcją, która odróżnia programowanie funkcyjne od programowania proceduralnego, jest brak efektów ubocznych. Oznacza to, że funkcje, które piszesz, nie opierają się na danych spoza funkcji ani ich nie aktualizują. Jak zobaczysz w poniższych przykładach, można zmienić strukturę problemu, aby można go było rozwiązać za pomocą funkcji bez efektów ubocznych, które znacznie lepiej nadają się do wykonywania równoległego.

Pętle for

W Earth Engine nie zaleca się używania pętli for. Te same wyniki można osiągnąć za pomocą operacji map(), w której określasz funkcję, którą można zastosować niezależnie do każdego elementu. Dzięki temu system może rozdzielić przetwarzanie na różne maszyny.

Poniższy przykład pokazuje, jak za pomocą map() utworzyć listę kwadratów liczb z innej listy:

Edytor kodu (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]

Warunki if/else

Innym częstym problemem, z którym borykają się nowi użytkownicy przyzwyczajeni do paradygmatu programowania proceduralnego, jest prawidłowe używanie operatorów warunkowych if/else w Earth Engine. Interfejs API udostępnia algorytm ee.Algorithms.If(), ale zdecydowanie odradzamy jego używanie na rzecz bardziej funkcjonalnego podejścia z wykorzystaniem map() i filtrów. Earth Engine używa wykonywania odroczonego, co oznacza, że ocena wyrażenia jest opóźniona do momentu, gdy jego wartość jest rzeczywiście wymagana. W niektórych przypadkach ten typ modelu wykonywania będzie oceniać zarówno prawdziwe, jak i fałszywe alternatywy instrukcji ee.Algorithms.If(). Może to prowadzić do dodatkowych obliczeń i wykorzystania pamięci, w zależności od wyrażeń i zasobów wymaganych do ich wykonania.

Załóżmy, że chcesz rozwiązać wariant powyższego przykładu, w którym zadaniem jest obliczenie kwadratów tylko liczb nieparzystych. Poniżej przedstawiamy funkcjonalne podejście do rozwiązania tego problemu bez warunków if/else:

Edytor kodu (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]

Ten paradygmat jest szczególnie przydatny podczas pracy z kolekcjami. Jeśli chcesz zastosować do kolekcji inny algorytm na podstawie określonych warunków, najlepiej najpierw przefiltrować kolekcję na podstawie warunku, a następnie map() inną funkcję do każdego z podzbiorów. Dzięki temu system może wykonywać operacje równolegle. Na przykład:

Edytor kodu (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());

Iteracja skumulowana

Może być konieczne wykonanie operacji sekwencyjnej, w której wynik każdej iteracji jest używany przez kolejną iterację. Earth Engine udostępnia do takich zadań metodę iterate(). Pamiętaj, że iterate() jest wykonywany sekwencyjnie, więc w przypadku dużych operacji będzie działać powoli. Używaj go tylko wtedy, gdy nie możesz użyć map() i filtrów, aby uzyskać pożądany wynik.

Dobrym przykładem zastosowania iterate() jest tworzenie ciągu liczb Fibonacciego. Każda liczba w ciągu jest sumą 2 poprzednich liczb. Funkcja iterate() przyjmuje 2 argumenty: funkcję (algorytm) i wartość początkową. Sama funkcja otrzymuje 2 wartości: bieżącą wartość w iteracji i wynik poprzedniej iteracji. Poniższy przykład pokazuje, jak zaimplementować ciąg Fibonacciego w Earth Engine.

Edytor kodu (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]

Teraz, gdy masz już dobrą znajomość pojęć związanych z JavaScriptem, możesz zapoznać się z samouczkiem dotyczącym interfejsu API, aby dowiedzieć się więcej o funkcjach geoprzestrzennych interfejsu Earth Engine API.