Rozszerzanie/ miksowanie klas w Javie 6/ 8, Scali i C#

Odpowiedz Nowy wątek
2011-08-06 02:08
2

Wraz z Javą 8, oprócz lambd (domknięć itp jak zwał tak zwał), mają nadejść metody rozszerzające, tzw "defender methods": http://openjdk.java.net/projects/lambda/ (podpunkt o tej samej nazwie).

Naszło mnie na zrobienie pewnego rodzaju porównania pomiędzy wspomnianymi w tytule językami jeśli chodzi o lokalne rozszerzanie funkcjonalności klas albo też wielodziedziczenie.

Defender methods to coś jak delegaty do statycznych metod rozszerzających (takich jak z C#). Różnica pomiędzy podejściem Javy i C# jest taka, że w Javie defender methods mają być wirtualne i dodaje się je do interfejsu. Jako, że w Javie można dziedziczyć po wielu interfejsach naraz to owe defender methods dadzą namiastkę mixinów ze Scali (chociaż dość kiepską namiastkę). Ponadto w dokumencie jest dość mocna sugestia, że defender methods będą zaimplementowane za pomocą invokedynamic. Jak dla mnie to dość odbieganie od pewnych założeń Javy, tzn statyczności konstrukcji językowych. Mimo wszystko defender methods są zaprojektowane tak, aby były maksymalnie kompatybilne z obecnym stylem pisania kodu Javowego.

Rozszerzanie ze Scali za pomocą konwersji implicit czy też kompozycja traitów jest wg mnie chyba najlepszym rozwiązaniem. Jest chyba najbardziej skalowalne, elastyczne i spójne, ale dalej jest całkowicie statyczną konstrukcją.

Z tego co do tej pory wydumałem (a że jest dość późno to możliwe że doszedłem do złych wniosków) wynika, że zarówno podejścia z C# jak i Javy 8 mają wady w stosunku do podejścia ze Scali. Konwersja implicit w Scali powoduje, że jedną linijką można dodać do jakiegoś typu wszystkie metody z innego typu. W Javie 8 czy C# trzeba zaklepać cały interfejs delegatów (extension methods). Podejście z C# jest całkowicie statyczne, w przeciwieństwie do podejścia z Javy 8, a więc jest zdecydowanie najgorsze pod względem skalowalności. Nie widzę jednak jak za pomocą defender methods rozszerzyć np Integera lokalnie o metody np operujące na liczbach urojonych. Z tego co wyczytałem więc extension methods z C# są właśnie po to, aby sobie lokalnie rozszerzyć dany typ o nowe metody, a defender methods z Javy 8 są po to, aby móc rozszerzać interfejsy bez potrzeby zmian w implementacjach. Konwersje implicit i wielodziedziczenie po traitach w Scali rozwiązują oba problemy.

Podsumowując: moim zdaniem podejście z C# (extension methods) to taka uboga wersja konwersji implicit ze Scali natomiast podejście z Javy 8 to dodanie namiastki mixinów ze Scali z zachowaniem kompatybilności wstecznej.

Co sądzicie o podejściach w tych językach, względnie w innych językach statycznie typowanych? MSZ extension methods z C# to co najwyżej oszczędzają kilka znaków przy wywołaniu metod, jako że zamiast ob.extensionMethod() można zrobić ext.extensionMetdhod(ob), gdzie ext to obiekt dostarczający dodatkowych metod. Defender methods z Javy natomiast dają możliwość bardzo ograniczonego wielodziedziczenia, co może znacząco zmniejszyć ilość metod do zaimplemetowania czy wydelegowania.

Nie wiem jaka jest polityka dotycząca .NETa, ale w Javie autorzy skupiają się na kompatybilności wstecznej. Defender methods są dlatego, że chcą do kolekcji dodać takie funkcje jak map(), forAll(), filter() etc nie powodując błędów kompliacji już istniejących wyspecjalizowanych, zewnętrznych implementacji kolekcji. W .NETu być może jest tak, że pod każdy numerek trzeba pisać inną wersję bibliotek/ aplikacji - nie wiem.

Kozioł i Królik lubią zabawy z zaawansowanymi funkcjonalnościami języków, więc ich zdanie by mnie ciekawiło. Deus pewnie pokazał by, że w Haskellu można to wszystko zaimplementować 30 x szybciej, ale niestety się już nie pojawia.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.

Pozostało 580 znaków

2011-08-06 02:57
0
Wibowit napisał(a)

MSZ extension methods z C# to co najwyżej oszczędzają kilka znaków przy wywołaniu metod, jako że zamiast ob.extensionMethod() można zrobić ext.extensionMetdhod(ob), gdzie ext to obiekt dostarczający dodatkowych metod.

Niby tak, ale ładniej (bardziej obiektowo) wygląda wygląda użycie ob.extensionMethod(), no i jakoś tak łatwiej chaining zrobić.

Defender methods są dlatego, że chcą do kolekcji dodać takie funkcje jak map(), forAll(), filter() etc nie powodując błędów kompliacji już istniejących wyspecjalizowanych, zewnętrznych implementacji kolekcji.

Obstawiam, że taki był też cel wprowadzenia extension methods do C#. Trochę tych metod zresztą naprodukowali: http://msdn.microsoft.com/en-[...]y/system.linq.enumerable.aspx (chociaż wolę te: http://msdn.microsoft.com/en-[...].linq.parallelenumerable.aspx) Dodanie tych metod nie powoduje też żadnych błędów kompilacji, bo niby jakie?

W .NETu być może jest tak, że pod każdy numerek trzeba pisać inną wersję bibliotek/ aplikacji - nie wiem.

Dodanie nowych przestrzeni, klas, metod czy elementów języka, nie powoduje konieczności przepisania od nowa całego kodu na świecie, to byłby jakiś absurd.

W każdym razie gratuluję Javowcom, że będą teraz mogli robić to, co reszta świata robi od 4 lat. ;)

edytowany 1x, ostatnio: somekind, 2011-08-06 02:59
Pokaż pozostałe 3 komentarze
Zależy ile masz rdzeni. ;) Dzięki np. Parallel.For zmieniasz jedną linijkę swojego kodu i Twój algorytm staje się magicznie wielowątkowy. Ręcznie miałbyś więcej pracy. Do tego jeszcze parę miłych dodatków, jak: Enum.HasFlag, Guid.TryParse oraz BigInteger i Complex. - somekind 2011-08-06 22:05
Jasne że są świetne i ich mi brakuję (piszę w 3.5 bo przyzwyczaiłem się do vs 2008), ale to raczej zmiany w bibliotece standardowej (nie wiem tylko czy da się Enum.HasFlag zaimplementować obecnie po ludzku). Zmiany w języku to chyba głównie dynamic który mi nieszczególnie podpada. - msm 2011-08-06 22:11
Akurat dynamic rozwiązuje odwieczny problem tworzenia generycznego kalkulatora. ;) Do tego słyszałem od znajomych, ze ponoć znacznie uprzyjemnia pracę z COMami. - somekind 2011-08-06 22:19
O, można na nim używać operatorów? Czyli może nie jest aż tak beznadziejny :). Z drugiej strony jest tak samo wydajny jak robienie tego samego ręcznie przez refleksję więc kwestia obliczeń na generykach dalej otwarta ;) A co do COMów to nie wiem, nie używałem. - msm 2011-08-06 22:25
Na dynamic można używać wszystkiego i wszystko się skompiluje. Gorzej z działaniem później. :P - somekind 2011-08-06 22:31

Pozostało 580 znaków

2011-08-06 15:54
0
somekind napisał(a)

Różnicy między defender a extensions na pierwszy rzut oka za bardzo nie widzę, bo jak rozumiem służą do osiągnięcia tego samego.

Cel niby jest podobny - rozszerzenie funkcjonalności kolekcji - ale idea działania znacznie inna. Extension methods są do lokalnego rozszerzania funkcjonalności danej klasy (a więc można sobie dodać metody do klas, które są final, tzn nie można po nich dziedziczyć), a defender methods do dodawania funkcjonalności do interfejsów bez konieczności zmian w implementacjach. Extensions methods są całkowicie statyczne (a przez to niewirtualne), a defender methods są wirtualne i pozwalają nawet na ograniczone wielodziedziczenie. Jak widać, podejścia działają na różnych frontach.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.

Pozostało 580 znaków

2011-08-06 19:51
msm
0

Zacznijmy od tego że Extension Methods to, jak napisał somekind, nie są i nigdy nie miały być namiastką traitów. To tylko syntax sugar, wszyscy to wiedzą i wszyscy się z tym zgadzają.

Ja dalej nie rozumiem czym się różnią EM od DM - jeszcze nie przeczytałem dokumentu ale za chwilę się za to zabieram. Na razie przykład w C# EM dla interfejsu:

interface IFoo
{
    int A { get; }
    int B { get; }
}
 
static class FooExtensions
{
    public static int Sum(this IFoo f)
    {
        return f.A + f.B;
    }
}
 
class Bar : IFoo
{
    public int A { get { return 1; } }
    public int B { get { return 2; } }
}
 
class Baz : IFoo
{
    public int A { get { return 6; } }
    public int B { get { return 5; } }
}
 
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Bar().Sum());
        Console.WriteLine(new Baz().Sum());
        Console.ReadKey(true);
    }
}

Tak to wygląda w Javie?

Co do domieszek (mixinów, traitów, to chyba jedno i to samo?) w C# jest do tego biblioteka re-mix (której zresztą planuję gdzieś użyć żeby się przyjrzeć ale jeszcze mi się nie udało):
http://remix.codeplex.com/
Możliwości ma spore chociaż, przyznaję, z lekkim narzutem dodatkowego kodu w porównaniu do Scali.

edit: BTW. deus stwierdziłby raczej że w Common Lispie domieszki i 100 innych ficzerów jest od 26 lat... I miałby rację [diabel]
Istnieje teoria że wszystkie rzeczy dodawane jako nowości do języków programowania istnieją od dawna w (Common) Lispie i, jak na razie, się potwierdza. :]

edytowany 5x, ostatnio: msm, 2011-08-06 19:59
No to czytaj ten artykuł :) W Javie oba interfejsy mogłyby mieć dodaną metodę Sum z domyślną implementacją, ponadto mogłyby mieć różne domyślne implementacje, a nawet klasy/ interfejsy dziedziczące mogłyby nadpisać te defender methods z interfejsów. - Wibowit 2011-08-06 19:58
W C# też klasy mogłyby mieć dodaną domyślną implementację! Problemem jest to że Extension Method by jej nie nadpisał ":C. OK, już czytam :] - msm 2011-08-06 20:01
Ale o to chodzi, że interfejsy (nie klasy) w Javie 8 mają dostarczać domyślnych implementacji, tzn posiadać domyślne delegaty (czy jak to tam nazwać). - Wibowit 2011-08-06 20:06
Chyba nie rozumiem - interfejsy mają mieć zaimplementowane metody? o.O - somekind 2011-08-06 22:33
Chyba nic nie rozumiesz. Przeczytaj ten dokument: http://cr.openjdk.java.net/~briangoetz/lambda/Defender%20Methods%20v3.pdf . Nie jest on, ani długi, ani jakiś specjalnie skomplikowany. - Wibowit 2011-08-07 11:06

Pozostało 580 znaków

2011-08-07 11:13
1

Skoro somekindowi nawet się nie chce zajrzeć do PDFa, wklejam z niego przykładowy kod:

public interface Set<T> extends Collection<T> {
public int size();
// The rest of the existing Set methods
 
public extension T reduce(Reducer<T> r)
default Collections.<T>setReducer;
}

Do interfejsu Set została dodana nowa metoda, reduce(Reducer) wraz z wydelegowaniem do statycznej metody implementującej tą metodę. Interfejsy czy też klasy rozszerzające Set mogą przeciążyć tą defender method, tzn wydelegować do innej statycznej metody. Istniejące implementacje interfejsu Set projektowane jak jeszcze nie było wiadomo o metodzie reduce dalej będą się kompilować bez błędu.

Dalej DM wyglądają dla was jak EM? Może mam po raz piąty czy któryś tłumaczyć?


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
Nie, to że mi się nie chce, po prostu w tygodniu zajmuję się remontem łazienki, a niedzielę spędzam na wycieczkach po marketach budowlanych. :P Ale skoro go streściłeś i wyjaśniłeś z przykładem, to chyba już nie będę go czytał. ;) Faktycznie, teraz rozumiem różnice. A masz jakiś praktyczny przykład, w którym przydałaby się możliwość przeciążania tej defender method? - somekind 2011-08-07 14:38
W zasadzie to jakikolwiek przypadek w którym sensowne jest normalne dziedziczenie, ale nie chcemy zerwać kompatybilności rozszerzając interfejsy w normalny sposób. Dajmy na to liczenie części wspólnej dwóch map. Zależnie czy to HashMapa czy ConcurrentSkipListMap a może TreeMap, tą metodę definiuje się inaczej. Ponadto jeśli np jakiś interfejs MultiMap rozszerza interfejs Map to wtedy to liczenie musi być przeciążone. - Wibowit 2011-08-07 14:46
Ja też już widzę różnicę, dokument przeczytałem (no, przejrzałem z czytaniem ciekawszych kawałków). Nie zrozumiałem na początku bo wyglądało jakbyś opisał DM jako EM dla interfejsów. - msm 2011-08-07 19:05

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0