środa, 7 listopada 2012

Iterables i reduce

Ostatnio uczestnicząc w kursie "JavaScript dla Javowców" doznałem pewnego olśnienia. Otóż JavaScript, którego nadal nie lubię (choć już trochę bardziej szanuje), ma kilka dość dobrych pomysłów, które warto przenieść do świata Javy.

Konkretnie mówię tu o funkcyjnym podejściu do operacji na tablicach/hashmapach w JS. Ktoś na szczęście już dawno wpadł na ten pomysł i mamy bibliotekę Google Guava, która jest dość dobrze poważana w świecie Javy.

Klika analogii JS => Java:

array.map(...) => Iterables.transform(...)
array.filter(...) => Iterables.filter(...)

W Guavie brakuje mi tylko 1 funkcji do pełni szczęścia, tj. odpowiednika funkcji array.reduce(...). Teoretycznie jest w planach jej prowadzenie, ale póki co jej nie ma, więc można szybko stworzyć własną implementacje.

import com.google.common.base.Preconditions;

public class Iterables {

 /**
  * redukuje przekazany {@link Iterable} do obiektu A
  * @param iterable co redukujemy
  * @param initialValue wartosc inicjalna, najlepiej neutralna dla danej                 operacji 0 dla dodawania, 1 dla mnożenia, itd 
  * @param reducer funkcja redukująca
  * @return wartosc zredukowana
  */
 public static <A, B> A reduce(Iterable<B> iterable, A initialValue, Reducer<A, B> reducer) {

  Preconditions.checkNotNull(initialValue);
  A result = initialValue;
  for (B b : iterable) {
   result = reducer.reduce(result, b);
  }
  return result;
 }
}

/**
 * funkcja redukująca
 * @param <A> klasa obiektu docelowego
 * @param <B> klasa obiektu wejsciowego
 */
public interface Reducer<A, B> {

 /**
  * redukuje parametr wejsciowy do docelowego
  *
  * @param to aktualny obiekt
  * @param from obiekt wejsciowy
  * @return zredukowany obiekt docelowy
  */
 A reduce(A current, B from);
}

I samo wykorzystanie:
List<Integer> someNumbers = Lists.newArrayList(1, 2, 3);
Reducer<Integer, Integer> sum = new Reducer<Integer, Integer>() {

 @Override
 public Integer reduce(Integer current, Integer from) {
  return current + from;
 }
};
Integer sumValue = Iterables.reduce(someNumbers, 0, sum);
assertThat(sumValue).isEqualTo(6);

Niby nic wielce odkrywczego, ale taka jest cała Guava, która proste rzeczy zmienia w jeszcze prostsze lub też "standaryzuje" pewne operacje.

Jeśli komuś takie (funkcyjne) podejście nie bardzo pasuje, to radziłbym się zacząć przyzwyczajać. W kolejnej wersji Javy prawdopodobnie wejdą wyrażenia lambda, więc funkcyjność wkroczy do "czystej" Javy pełną parą.