wtorek, maja 17, 2011

Przepływ sterowany danymi - A takie Java'owe coś ;-)

Na GeeCONie było dużo o Groovy i jeden z prowadzących pokazał pewien trick w Groovy (to chyba on był ;-)).
Napisał programik w stylu:
group.task(
e = m*c^2
println("Your energy is "+e+" J")
}
group.task(
print("Your mass in kg:")
m=getInt()
}
group.task(
h=sqrt(mass/25)
print("You should have at least "+h+" m tall")
}
Który to programik zapytał o masę ciała i policzył energię z wzoru E=mc2, oraz minimalny wzrost. Niby nic, ale zrobił to w taki sposób, że zmartwieniem programisty było jedynie zebranie danych i dostarczenie wzorów, a już wszystko samo się policzyło ;-)

Zrobiło to na mnie wrażenie i nawet myślałem, że to jest ficzer Groovy, okazało się później, że to tylko specjalna biblioteka... to sobie napisałem coś takiego w Java'ie ;p

Trochę jest rozwleklejsze bo w Java'ie nie mamy jeszcze (do Java'y 8... która powinna pojawić się w przyszłym roku....) funkcji lambda, trzeba więc każdego taska napisać jako oddzielną implementację interfejsu Runnable:
start(new Runnable() {
public void run() {
e.setValue(m.getValue()*C*C);
System.out.println("Your energy is "+e.getValue()+" J");
}
});
start(new Runnable() {
public void run() {
h.setValue(Math.sqrt(m.getValue()/25.0));
System.out.println("You should be at least "+h.getValue()+" m tall.");
}
});
start(new Runnable() {
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Your mass in kg:");
try {
m.setValue(Double.valueOf(br.readLine()));
} catch (IOException e) {
e.printStackTrace();
}
}
});


Funkcja start(Runnable) tworzy po prostu wątek i go odpala.

Tajemnicze zmienne e, m i h są tak zadeklarowane:
final Promise<Double> m = new Promise<Double>();
final Promise<Double> e = new Promise<Double>();
final Promise<Double> h = new Promise<Double>();
final double C = 299792458;
Dodatkowo mamy tutaj definicję wartości prędkości światła ;-)

Teraz najważniejsze, czyli tajemnicza klasa Promise<T>:
public static class Promise<T> {
private T value;
private Object lock = new Object();
private boolean hasValue = false;

public T getValue() {
while (!hasValue) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
};
}
}
return value;
}

public void setValue(T val) {
value = val;
hasValue = true;
synchronized (lock) {
lock.notifyAll();
}
}
}

To w niej odbywa się najzabawniejsze ;-) Czyli blokowanie wykonania kodu związanego z pobraniem wartości do momentu gdy ta wartość będzie dostępna ;-)
Mamy np. w miejscu obliczania energii coś takiego:
e.setValue(m.getValue()*C*C);
gdzie do obliczenia wartości energii potrzebujemy masy, ale masa będzie dostępna dopiero w momencie gdy użytkownik ją wprowadzi... Po to więc w getValue() w pętli while testujemy czy dana "obietnica" ma już wartość, jeżeli jej nie ma to każemy kodowi czekać na blokadzie.
Dopiero gdy wartość zostanie ustawiona przy pomocy setValue(T) na blokadzie wołane jest notifyAll(), które budzi wszystkie wątki czekające na daną blokadę (w dowolnej i nieokreślonej kolejności, ale to urok niedeterministycznych wątków ;-)).

Szczerze nie widzę zastosowania dla takiego czegoś, ale możliwe, że czasem może się przydać ;-) Jedynym problemem jest to, że przy odpowiednich zależnościach można dość łatwo doprowadzić do deadlocka ;-)

Źródło całego programu (liczące jakieś 67 linii ;-)) jest tutaj.


Podobne postybeta
Potfór ;-) czyli generator z yield w Java'ie
Inercja i koło wielokrotnego wynajdywania, czyli radosne macki piekieł w kodzie [alem pojechał w tytule ;-)]
Modale dobre - confirm dla Androida :-)
Niecne wykorzystanie refleksji... czyli jak poszukać tekstu w drzewie obiektów? ;-)
wait() i notify()/notifyAll() - najbardziej nierozumiane metody klasy Object ;-)