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

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.

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-us/library/system.linq.enumerable.aspx (chociaż wolę te: http://msdn.microsoft.com/en-us/library/system.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. ;)

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.

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. :]

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ć?

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