Thanks to visit codestin.com
Credit goes to oblac.rs

Sofronije vs. monade

Ništa ti, sinak, ono sa monadama ne razumem. Oš li mi, ovaj, ti to malo pojasniti? Pratim kurs “Postani Java Pevac Ekspert za 21. dan kukurikanja”; još koje jutro i gotov sam! Hajde sinak, neću ni reč reći, sve ću te slušati, ja kada ćutim - onda ćutim, ništa ne govorim, ja sam lepo vaspitan, jednom umalo nisam umro od gladi jer nisam hteo da kažem da sam gladan…

- (uzdah) Hajde, Sofronije, da probamo primer koji sam jednom video u onom YT kokošinjcu. Ovako nekako ide.

Vidi ove dve funkcije:

public Integer square(Integer i) {
  return i * i;
}
public Integer increment(Integer i) {
  return i + 1;
}

Funkcije možeš da kombinuješ, na primer:

increment(square(3)); // 3*3+1=10

Sada zamisli da je kvočka Mileva zatražila da prati šta se dešava tokom računanja, kako bi proverila da je sve kako treba.

- Vala baš, sinak, ta Mileva samo zrna broji vasceli dan, umesto muža da traži. Dobro, sinak, kako ćemo da to rešimo?

- Menjamo funkcije, Sofronije; treba da nam vrate više toga nazad. Uvodimo nam novi tip kojim se vraćaju podaci nazad iz funkcija. Na primer ovako:

public class IntegerWithHistory {
  public final Integer value;
  public final String[] history;
  // ctor...
}

public IntegerWithHistory square(Integer i) {
  return new IntegerWithHistory(
    i * i,
    new String[] {"square: " + i}
  );
}
public IntegerWithHistory increment(Integer i) {
  return new IntegerWithHistory(
    i + 1,
    new String[] {"increment: " + i}
  );
}

- Ček, ovaj, ček malo, sinak… pa kako ćemo sada da kombinujemo funkcije, sto mu petlova?

- Bravo Sofronije, dobro si primetio! Moramo da promenimo i ulazni tip za funkcije:

public IntegerWithHistory square(IntegerWithHistory i) {
  return new IntegerWithHistory(
    i.value * i.value,
    join(i.history, "square: " + i.value)
  );
}
public IntegerWithHistory increment(IntegerWithHistory i) {
  return new IntegerWithHistory(
    i.value + 1,
    join(i.history, "increment: " + i.value)
  );
}

Sada je kombinovanje moguće:

IntegerWithHistory i =
  increment(square(
    new IntegerWithHistory(3, new String[] {}))
);
System.out.println(i.value);
System.out.println(Arrays.toString(i.history));

- Stani, ovaj, stani malo sinak: pa zar pravimo objekat IntegerWithHistory za svaki broj sa kojim radimo?

- Pravimo, Sofronije, nego šta. Ali nećemo stalno ovako pisati, svakako, nego pravimo funkciju za to. Neka se zove wrap(). To je nekakav statički konstruktor našeg tipa. Najviše ima smisla dodati je u sam IntegerWithHistory:

public class IntegerWithHistory {
  public final Integer value;
  public final String[] history;
  // ctor
  public static IntegerWithHistory wrap(Integer i) {
    return new IntegerWithHistory(i, new String[] {});
  }
}
//...
IntegerWithHistory i =
  increment(square(IntegerWithHistory.wrap(3)));

Okej, to je bilo lako. Sofronije, pazi sada: zagledaj se u ove dve funkcije square i increment, da li nešto primećuješ?

- Kao da su, možda, slične ko jaje jajetu?

- Tako je. Hajde da označimo šta je različito:

Ako bi ove različite blokove nekako mogli da prosledimo kao argumente, ostala bi samo jedna funkcija!

- Znam, ovaj, znam! Daj meni da to uradim, dečače; duda-duda-de, otvori oči i gledaj:

public IntegerWithHistory calculate(
    IntegerWithHistory i,
    Function<Integer, Integer> operation,
    String operationName) {
  return new IntegerWithHistory(
    operation.apply(i.value),
    join(i.history, operationName + ": " + i.value)
  );
}

- Vrlo blizu, Sofronije; ali ne. Sad ti treba puna pažnja; batali zafrkavanje tog kera i gledaj ovamo.

Već imamo strukturu IntegerWithHistory koja nam čuva vrednost i nekakav tekst - hajde da to zapišemo nekako ovako: {Integer, String}. S druge strane, treba nam funkcija za vrednost i taj isti tekst: {(Integer→Integer), String}. Kako da iz prvog dobijemo ovo drugo? Kako da iskoristimo istu strukturu (tip) IntegerWithHistory, a da imamo i funkciju kojom menjamo vrednost?

{Integer, String} => {(Integer→Integer), String}

Tako što ćemo (Integer→_) da izvučemo iz drugog dela izraza i primenimo ga na prvi :))) Šalim se malo, ali i ne. Drugim rečima, treba nam funkcija: (Integer→{Integer,String}):

Function<Integer, IntegerWithHistory> increment =
  i -> new IntegerWithHistory(i + 1, "increment: " + i);
Function<Integer, IntegerWithHistory> square =
  i -> new IntegerWithHistory(i * i, "square: " + i);

Videli smo da nije dovoljno da imamo funkciju koja samo menja vrednost, jer ne zna ništa o istoriji. Na ovaj način koristimo postojeći tip IntegerWithHistory (vrednost i istorija) i dodajemo transformaciju vrednosti! Time dva potrebna argumenta za funkciju calculate postaju jedan:

public IntegerWithHistory calculate(
    IntegerWithHistory i,
    Function<Integer, IntegerWithHistory> operation) {
  final IntegerWithHistory a = operation.apply(i.value);
  return new IntegerWithHistory(
    a.value,
    join(i.history, a.history)
  );
}

Koristi se ovako:

IntegerWithHistory i = calculate(
  calculate(IntegerWithHistory.wrap(3), square),
  increment);

- Auuu, sinak, moraću malo da, ovaj, da razmislim o svemu. Vidim šta si uradio, jasno mi je; mada i nije nešto lepo. Nego, jel’ smo gotovi? Mileva me zove u kokošinjac na čaj, ostala je sama.

- Hajde još samo da prebacimo calculate() u IntegerWithHistory. To je bar lako:

public static class IntegerWithHistory {
  public final Integer value;
  public final String[] history;
  // ctor

  public static IntegerWithHistory wrap(Integer i) {
    return new IntegerWithHistory(i);
  }

  public IntegerWithHistory calculate(Function<Integer, IntegerWithHistory> operation) {
    final IntegerWithHistory a = operation.apply(value);
    return new IntegerWithHistory(
      a.value,
      join(history, join(history, a.history))
    );
  }
}

Gledaj kako sada izgleda program:

val increment =
  i -> new IntegerWithHistory(i + 1, "increment: " + i);
val square =
  i -> new IntegerWithHistory(i * i, "square: " + i);

val i = wrap(3).calculate(square).calculate(increment);

Nije li sjajno!?

- Kukurikuuuu jeste, dečače, sto mu gromova, baš u stilu Dejvida Kroketa! Nego, sinak, gde su ti tu te monade druškane, šta me sada zamajavaš sa ovim računanjima?

- Sofronije, upravo smo napravili monadu: IntegerWithHistory. Funkcija wrap je unit(). Funkcija calculate je flatMap(). Napravili smo kontejner za broj koji nosi dodatni kontekst oko njega - istoriju.

- Auuu, prevca ti… Ovaj, dečko, imaš li još onih mudrijaških knjiga?

🧧
Nisam definisan svojim stavovima. Stavove usvajamo, menjamo, nadograđujemo, ali oni ne čine nas same. Manje je važno da li se slažemo,
koliko da se razumemo.
> ČASTI KAFU <