[nem-pl] Uwagi różne

Marcin 'Qrczak' Kowalczyk qrczak at knm.org.pl
Fri Feb 20 17:53:20 CET 2004


W liście z pią, 20-02-2004, godz. 16:23, Kamil Skalski pisze:

> Nie będę tutaj zaczynał dyskusji nad tym czy statycznie typowane języki
> są lepsze czy gorsze niż dynamicznie typowane.

Doceniam oba paradygmaty; każdy ma zalety nieosiągalne dla drugiego.

Przy czym dobry statyczny system typów trudniej jest zaprojektować niż
dynamiczny.

> > Bynajmniej. Chciałem dodać nową metodę do istniejącej klasy, choć czuję,
> > że to jest u Was niemożliwe. Jeśli to jest niemożliwe, to co się robi,
> > jeśli chcę dodać nowy interfejs do isniejących już klas (np. do klas
> > standardowych)?

> Zmiana klas standardowych jest chyba nierozsądnym pomysłem, tym bardziej 
> że trzebaby wtedy wygenerować bibliotekę,

Sęk w tym, że Nemerle (tak jak C#) kwalifikuje dodanie metody jako
zmianę klasy. W różnych językach, statycznie typowanych bądź nie,
dodanie czegoś o podobnych właściwościach co metoda (wybór wersji
ogólnej operacji w zależności od typu argumentów) nie wymaga zmiany
klasy. Zdaję sobie sprawę, że w .NET inaczej jest trudno z powodów
technicznych - chcę Wam uzmysłowić ograniczenia :-)

Systemy, które mam na myśli, to klasy Haskella (stosowane też w Cleanie
i Mercurym) i funkcje generyczne (CLOS, Dylan, Cecil i mój język).

> > BTW, jak zrobić coś takiego: definiuję typ np. listy, którego obiekty są
> > haszowalne o ile typ elementów jest haszowalny - a jeśli nie są, to
> > nasza lista też nie będzie haszowalna, ale w ogóle będzie używalna dla
> > tych elementów? Tego też nie można np. w Eifflu, a można w statycznie
> > typowanym Haskellu.
> 
> Nie wiem co dokładnie rozmiesz pod pojęciem "haszowalny", ale zastępując 
> to dowolnym abstrakcyjnym słowem... jest możliwe.

Jak? W Nemerle definicja polimorficznego typu musi od razu określić,
jakie interfejsy będą przez ten typ implementowane. Nie może uzależniać
implementowania interfejsu od cech parametrów typowych.

> > A, czyli już w momencie pisania klasy trzeba się zdecydować...
> 
> To jest konsekwencja statycznego typowania.

Nic podobnego, statycznie typowany Haskell może.

instance Hashable a => Hashable [a] where
   hash xs = foldl combineHash 0 xs

Teraz [1,2,3] jest haszowalne, a [\x -> x+1, \x -> x+2] nie jest.

Zamiast haszowania można sobie oczywiście podstawić serializację w różne
formaty. Serializacja jest zresztą trochę lepszym przykładem, bo po
pierwsze obiektów niektórych typów w żaden sensowny sposób nie da się
serializować, a po drugie trudno wymagać od autora jakiegoś typu
kolekcji, żeby przewidział wszystkie protokoły serializacji.

Jakie interfejsy implementuje typ listy i dlaczego akurat takie? A jeśli
w innym miejscu programu zostanie wprowadzony jakiś interfejs, który
lista powinna móc implementować?

> > Jeśli użytkownik napisze
> >    def g = 5;
> >    m ()
> > gdzie m jest makrem, które higienicznie wprowadza definicję "g" (czyli
> > generuje po prostu kod "def g = 6", co jest oczywiście innym "g")
> > i w jego zakresie używa makra m', natomiast m' jest makrem, które
> > niehigienicznie odwołuje się do "g" (tzn. wstawia w kod $("g" : var)) -
> > to które "g" zostanie przez m' złapane?
> 
> oczywiście do "def g = 5;" czy jest w tym coś dziwnego?

To, że być może intencja była właśnie odwrotna. Taki design powoduje, że
znaczenie tego fragmentu:
   def g = 6;
   m' ()
zależy od tego, czy on został wpisany w głównym programie, czy w treści
makra! W pierwszym przypadku m' użyje tego g, a w drugim będzie go
szukać gdzie indziej.

Zanim wymyślę życiowy przykład, spytam o sytuację odwrotną. Co jeśli
makro niehigienicznie wprowadza identyfikator zamiast go łapać?
Konkretniej, niech makro m rozwija się do lokalnej definicji
niehigienicznego g, w zasięgu której wstawiony jest argument m (czyli on
widzi g). Niech makro m' rozwija się do użycia makra m, gdzie argument m
1. sam się odwołuje do g, ma je napisane w treści m'
2. zawiera argument m', który być może odwoła się do g

Pytanie: które z przypadków 1 i 2 złapią g wprowadzone przez m, a które
będą się odwoływać do ewentualnego g widocznego w przypadku 1 w miejscu
definicji m', a w przypadku 2 w miejscu użycia m'?

> No dobrze - z NewSymbol taki jest nasz obecny design (który jest jeszcze
> dość płynny, bo tak naprawdę jeszcze higieny nie zaiplementowaliśmy),

Nie szkodzi, ja jeszcze nie zacząłem implementacji makr...
Rozmawiam designie planowanym na przyszłość.

> A pamiętać o tym nie trzeba, kompilator się upomni, bo napisanie
> def y = <[ x ]> 
> spowoduje u nas komunikat "unbound object-program variable `x'"

Jak rozumiem, tylko jeśli x nie jest widoczne w miejscu definicji makra.

U mnie można to napisać. Ten błąd wyskoczy dopiero przy kompilacji
rozwinięcia makra - ale błędu nie będzie w ogóle, jeśli y będzie
wstawione w roli nazwy czegoś definiowanego, a wszystkie wystąpienia
w roli użycia będą w zakresie takiej definicji.

> > Aha, jak zgaduję to o to chodziło w kwalifikowaniu niekwalifikowanych
> > nazw użytych w makrach? Może jeszcze zadbacie, żeby rozwinięcie makra
> > mogło się do tego odwoływać nawet jeśli jest prywatne? 
> 
> Impossible in .NET. System bezpieczeństwa .NETu nie pozwala na to,
> po prostu kod wynikowy nie może mieć dostępu do czyichś prywatnych rzeczy.
> Makra są pod tym względem przezroczyste i to jest IMHO dobra rzecz.

Nie widzę zalety, poza tym że taki design był konieczny z innych powodów
(tzn. z powodu wymuszania prywatności na poziomie IL, a nie na poziomie
języka źródłowego). Jeśli jakaś funkcjonalność ma być udostępniona w
formie makra, to zmuszacie do tego, żeby były udostępnione również
wszystkie elementy, do których to makro się rozwija, nawet jeśli one nie
były planowane jako oficjalny interfejs. Być może np. te makra dbają o
bezpieczny sposób użycia tych elementów, a użycie ich w innej
konfiguracji może popsuć jakieś niezmienniki? Biblioteka nie ma jak się
przed tym zabezpieczyć.

Ogólnie nie jestem fanem wymuszania prywatności, tzn. nie uważam, żeby
fizyczne wymuszanie było ważne. Ale jeśli już mamy prywatność, to
eksportowanym makrom mogłaby nie przeszkadzać.

Przyznaję, że to drobiazg.

> Makr lokalnych rzeczywiście nie planujemy, ale to dodatkowo z innego powodu.
> Makra obecnie (i pewnie w przyszłości) muszą być skompilowane 
> (i najlepiej umieszczone w bibliotece) przed użyciem - ma to bardzo wiele
> zalet, ale i wad (ja cały czas myślę, że warto to kiedyś zmienić, ale o to 
> niech się Michał kłóci). Marka lokalne w takim modelu po prostu nie mają
> wielkiego sensu.

Ciekaw jestem, czy makro może się rozwinąć do definicji innego makra.

Oraz w ogóle z jakich funkcji może korzystać treść makra, tzn. które
pakiety i nazwy są dostępne w czasie kompilacji. Bo pewnie nie może
korzystać z funkcji właściwego programu napisanych powyżej; jeśli nie
może, to gdzie wpisać pomocniczą funkcję do użycia w makrze?

> > To makro jest higieniczne, tzn. nie komunikuje się z kodem używającym
> > makra za pomocą identyfikatorów, które nie zostały wprost napisane w
> > kodzie używającym makra. 
> 
> Reference{cytat który powinien przewijać się od kilku postów} ;-)
> Jeśli makra chcą się komunikować, to muszą to robić przez unikalne 
> nazwy, najlepiej dzielone w jakiejś globalnej zmiennej inicjalizowanej przez
> któreś z nich. Jeśli to nie o to co ci chodziło, to możesz podać przykład?

Nie rozumiem. Pewnie nie o to. Niehigieniczność jakiegoś makra polega
w ogólności na tym, że albo kod używający makra dostaje dostępny (po
użyciu makra-definicji bądź wewnątrz argumentów makra) identyfikator,
którego jawnie makru nie podał; albo odwrotnie, makro dostaje się do
umówionej nazwy, w obrębie definicji której makro musi być użyte, mimo
że nie przekazujemy makru tej nazwy wprost. Wypisywanie tupli nie jest
takim ani takim przypadkiem - nawet więcej, modulo system typów
zachowuje się jak zwykła funkcja.

> > to nazwa "loop" będzie prywatną sprawą tego makra, a jeśli wstawi
> >    let $(implicit :again) {...};
> >    $(implicit :again)()
> > to nazwa "again" będzie dostępna w treści makra. Można to zresztą
> > zapisać prościej $implicit.again (korzystając z równoważności dostępu
> > do pola i aplikacji do symbolu).
> 
> Rozumiem, że masz na myśli "lokalizowanie" generowanych unikalnych 
> symboli,

Raczej coś przeciwnego: generowanie symboli, które nie są unikalne,
tylko są rozpoznawane przez kod używający makra.

> ale nie widzę przewagi nad jednym, globalnym dla całego programu,
> systemem generacji symboli - żadnych kolorowań, po prostu symbole.

Jeśli makro jest użyte przez inne makro, to mamy dwa miejsca, które
można uznać za kontekst użycia pierwszego makra:
1. treść drugiego makra
2. kontekst użycia drugiego makra

Według Scheme i według mojego systemu łamanie higieny przez pierwsze
makro oznacza wsadzanie bądź pobieranie identyfikatorów z miejsca 1, ale
nie 2. Żeby makro forwardowało niehigieniczne identyfikatory, musi to
zrobić jawnie.

(To znaczy jest sztuczka, którą można to obejść - mianowicie użycia
samej nazwy drugiego makra w pierwszym makrze same mogą być
niehigieniczne - ale skutek uboczny jest taki, że w miejscu użycia
pierwszego makra drugie makro musi być dostępne, czego na oko nie widać.
Można np. zapomnieć je wyeksportować albo przykryć nazwę. Sztuczka nie
jest zalecana. Być może system kwalifikowania nazw coś na to poradzi;
nie wiem, czy dotyczy również kwalifikowania nazw makr.)

implicit jest funkcją niejawnie dostępną w treści makra, która zamienia
goły symbol na element składni, który łapie bądź jest łapany przez
identyfikator o takim samym brzmieniu, napisany w miejscu użycia
tego makra. Jeśli zamiast użycia tej funkcji napiszemy po prostu
identyfikator w cytacie, to będzie on pokolorowany i będzie miał
doczepione środowisko miejsca definicji makra; jeśli jego użycie okaże
się definicjią, to dzięki pokolorowaniu złapie tylko inne wystąpienia
tego samego identyfikatora z treści tego makra, a jeśli to użycie okaże
się odwołaniem do wcześniej zdefiniowanej nazwy, to wyciągnie ją ze
swojego środowiska, a nie z kontekstu użycia makra.

-- 
   __("<         Marcin Kowalczyk
   \__/       qrczak at knm.org.pl
    ^^     http://qrnik.knm.org.pl/~qrczak/





More information about the devel-pl mailing list