Nie jestem specjalistą w dziedzinie języków, teorii typów etc. Zawarte w poniższym poście sformułowania mogą być nieprecyzyjne a czasem nawet mijać się z prawdą. Najważniejszym dla mnie celem jest pokazanie pragmatycznych problemów.
Cały czas poznaję Scalę i muszę powiedzieć, że niektóre konstrukcje/rozwiązania mogą przyprawić czasem o ból głowy.
Ciemna strona mocy - czyli typy zależne od ścieżki (PDT - path-dependent types). Całość wynika z możliwości definiowania struktur wewnętrznych praktycznie bez ograniczeń. Można więc napisać:
trait A {
type X
class B {
trait C {
type Y
}
case class D {
}
}
}
"So far, so good" - można bowiem znaleźć analogię do podobnych konstrukcji z Javy, gdzie mamy 'member classes'. Nie dajmy się jednak zwieść pozorom - w Javie 'member class' jest określona w definicji typu i nie interesuje nas, przez jaką zmienną tam się dostaniemy. Raz zdefiniowana klasa wewnętrzna zawsze "pozostanie sobą". Nie ma więc problemu żeby napisać coś takiego:
abstract class A {
abstract class B {
abstract void and(B b);
}
abstract B foo();
}
abstract class C extends A {
A a;
{
a.foo().and(foo());
}
}
Wystarczy jednak, że zmienimy nieco składnię (i język programowania ;)), żeby uzyskać coś takiego...
abstract class A {
abstract class B {
def and(b : B)
}
def foo : B
}
abstract class C extends A {
val a : A
{
a.foo and foo
}
}
... plus piękny Syntax error:
type mismatch;
found : C.this.B
required: C.this.a.B
Niestety moja mała wiedza na temat Scali nie pozwala mi na ominięcie tego problemu inaczej niż przez zastosowanie brzydkiego hacka: "a.foo.asInstanceOf[B] and foo". Powyższa kwestia niestety nie jest ani czysto abstrakcyjna, ani wydumana. Wystarczy, że zaczniemy stosować w Scali pewne idomatyczne rozwiązania (zwłaszcza pewien rodzaj agregacji typów w bibliotekach - p. Parsers). Okazuje się, że będziemy wtedy niemal zmuszeni przy bardziej skomplikowanych strukturach do zastosowania dziedziczenia zamiast delegacji. Wynik nie jest ani ładny ani składny.
No comments:
Post a Comment