Historia kołem się toczy, więc po raz kolejny zabieram się do programowania bardziej funkcyjnego. Obejrzałem film Refactoring to Functional. a ponieważ jego autor Hadi Hariri pracuje obecnie dla JetBrains, to uznałem, że temat trzeba potraktować poważnie.
W C# mamy dobrodziejstwa Linq, jednakże istnieje, że jego uniwersalność jest okupiona dużym narzutem. Unikam zatem Linq w miejscach, gdzie może on być źródłem kłopotów wydajnościowych. Ponieważ nic nie może przebić piękna i prostoty pętli FOR, stosowałem ją tam gdzie podejrzewałem, że Linq może nie dać rady. Stąd też poniższy, przykładowy kod.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public double Length { get { double sum = 0; for (var i = 1; i < Points.Length; i++) { var t = Points[i - 1] - Points[i]; sum += t.Length; } return sum; } } |
W pętli obliczana jest sumaryczna długość linii łamanej określonej przez tablicę punktów Points . Może nie ruszyłbym tego kodu, gdyby nie druga inspiracja, film Michała Bartyzela, Jak zniszczyć swój kod — podstawy lingwistyki dla programistów.
Postanowiłem wykorzystać zalety programowania funkcyjnego ale nie w oparciu Linq, zwłaszcza że obliczenia bazują nie tyle na kolejnych elementach kolekcji co na parach sąsiadujących elementów. Zapytanie Linq zamieniające sekwencję elemntów na sekwencję par mogłoby być karkołomne i zaciemnić kod zamiast go uprościć, t.j.
1 |
Points.Skip(1).Zip(Points, (second, first) => new[] { first, second })..... |
Powyższy kod zainspirowany http://stackoverflow.com/questions/1624341/getting-pair-set-using-linq.
Kod własności Length zamieniłem na taki.
1 2 3 4 5 6 7 |
public double Length { get { return Points.FoldPairs(0.0, (accumulator, p1, p2) => accumulator + (p2 - p1).Length); } } |
z użyciem ExtensionMethod
1 2 3 4 5 6 7 8 9 |
public static TFold FoldPairs<TData, TFold>(this IList<TData> list, TFold initial, Func<TFold, TData, TData, TFold> foldMethod) { if (list==null || list.Count < 2) return initial; for (int i = 1, max = list.Count; i < max; i++) { initial = foldMethod(initial, list[i - 1], list[i]); } return initial; } |
Wadą tego rozwiązania jest to, że nie sprawdzam czy w trakcie działania metody FoldPairs lista źródłowa się nie zmieniła. Klasa List<T> posiada prywatne pole _version, które jest skrupulatnie wykorzystywane przez iterator. Pole to jednakże nie jest – poza mechanizmami refleksji – możliwe do uzyskania. Istnieje jednak bardzo wiele scenariuszy, w których rozwiązanie takie jest wystarczające. Zawsze można dopisać alternatywną wersję metody, która wykorzystując enumerator będzie czuwała nad niezmiennością listy.
Zaleta jest taka, że czytelność kodu jednak się poprawiła. W środku mam swoją pętlę for, więc nie korzystam z iteratorów, enumeratorów i innych elementów.
Węsząc za pętlami for
Przy okazji starałem się w prosty sposób przeszukać kod źródłowy w poszukiwaniu pętli for zaczynających się od zera. Prosty (choć nie wolny od wad) regexp, którego używłem wygląda tak:
1 |
for\s*\((var|int)[^=]+=\s*0 |