[nem-pl] Uwagi różne
Kamil Skalski
nazgul at omega.pl
Fri Feb 20 19:59:28 CET 2004
On Friday 20 of February 2004 17:53, Marcin 'Qrczak' Kowalczyk wrote:
> 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 :-)
Uhum, rzeczywiście. W .NET wszystko jest metodą w jakiejś klasie, więc
kombinując coś z nimi zmieniamy klasę. Ogranicza nas to o tyle, że nie możemy
zmieniać bibliotecznych klas i ich metod. Zawsze alternatywą jest
dziedziczenie, w końcu to OO. Tam możemy robić z klasami (za pomocą makr
i ręcznie) co nam się podoba.
> 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.
Aha, jeśli masz na myśli to, że instancjując List<'a> czymś co nie jest
hashable dostajemy listę niehashable, a jeśli jest to hashable to rzeczywiście
nie widzę sposobu. Konieczność implementowania interfejsu przez wszystko
nie jest ładna. No ale co tu dużo gadać, robi się dwie wersje tej listy i
ręcznie wybiera czego się używa. Można najwyżej sobie wyobrazić makro, które
przekształca każde List<int> , List<Foo>, itd. w ListHashable<Foo> jeśli Foo
jest hashable. Byłby to jakiś automatyzm, ale z drugiej strony czy nie lepiej
żeby użytkownik widział jakiego typu właściwie jest jego List<Foo>?
> 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.
Uhm, widzę chyba o co chodzi. To już raczej nie dotyczy makr. Ale argument
powyżej jest nadal aktualny.
> 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ć?
Dziedziczenie, OO itd. Makro może zmienić deklarację jakiejś klasy na
podstawie informacji znanych podczas kompilacji. Nawiasem mówiąc
rzeczywiście zrobienie MyList : List wydaje się prostszym rozwiązaniem niż
kombinowanie z makrami tutaj.
> 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
No ja zdaję sobie sprawę, że niehigienicznymi makrami można sobie zrobić
krzywdę taką, że nawet się nie zorientuje człowiek o co chodzi.
Prawdę mówiąc, moglibyśmy w ogóle zabronić niehigieniczności, wywalając
konstrukcję <[ $(id : var) ]>... bo właściwie nie widzę powodów żeby nie...
poza tym, że akurat w implementacji quotowań korzystam sobie w paru
miejscach z tego, co spokojnie da się ominąć. Prawdopodobnie są jakieś
sensowne zastosowania niehigieniczności, czyli wklejania gołych nazw
do kodu.
Z drugiej strony, ostatecznie aż takiej wielkiej krzywdy się łatwo nie zrobi,
bo najczęściej wszystko się wywali z jakimś niezrozumiałym błędem
podczas kompilacji. Najgorsze byłoby, gdyby niehigieniczność doprowadziła
do złapania zmiennej o zgadzających się typach itd. co doprowadziłoby
do niewykrywalnego błedu logicznego.
Z samym NewSymbol znacznie trudniej zrobić sobie krzywdę, choć oczywiście
i tu możliwe są najgorszego typu błędy. Dlatego chyba nie ma sensu
zabronienie $(id : var), bo to się czasami przydaje.
Moje pytanie jest w takim razie takie, czy wiesz jak uchronić się przed tymi
zakręconymi przypadkami które opisujesz.
W tej chwili nasza higieniczność ma robić:
- przemianowywanie <[ def x = ... x ]>
- rozwijanie nazw <[ ... x ]> gdzie x jest widoczne gdzieś w miejscu definicji
makra, na <[ A.B.x ]>
- zgłaszanie błędów we wszystkich pozostałych przypadkach
- $ pozwala na złamanie higieniczności, co powoduje że chyba nic
sensownego nie da się powiedzieć
> > spowoduje u nas komunikat "unbound object-program variable `x'"
>
> Jak rozumiem, tylko jeśli x nie jest widoczne w miejscu definicji makra.
Tak, ew. jeśli x jest widoczne jako lokalna zmienna w definicji makra,
to specjalny komunikat 'pomyliłeś zmienną z meta-zmienną, napisz $x'
>
> 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.
Często jest to tylko walka o to kiedy wyskoczy błąd - zawsze lepiej żeby
podczas kompilacji makra, niż kompilacji użycia makra.
np. <[ true + 1 ]> u nas się kompiluje, a nie będzie (prawda Michał?)
>
> Ciekaw jestem, czy makro może się rozwinąć do definicji innego makra.
Nie może. W ogóle nie można np. zrobić <[ <[ 1 ]> ]>
To znaczy tego nie można właśnie dlatego, żeby pozbyć się tych wszytkich
problemów żyjących na obrzeżach logik temoralnych (wybaczcie, to taka
nic nie znacząca dygresja ;-) ). Naszym celem jest prostota.
Myślę, że gdy to co już mamy wymyślone będzie w pełni działać, możemy
się zająć bardziej skomplikowanymi zastosowaniami, ale po kolei.
>
> 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?
Może wszystko. Tak naprawdę słówko macro powoduje ekspansję
podanej funkcji do klasy implementujacej interface IMacro z metodą Run,
do której poza kilkoma rzeczami wklejamy treść tej funkcji. Może ona zatem
używać i lokalnych funkcji i metod z zewnątrz (byle były juz skompilowane w
momencie uruchomienia makra).
Spójrz do nemerle/ncc/stdmacros.n i np. na makro regexp lub printf
> 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
Tak, oczywiście. I myślimy nad tym jak ułatwić użytkowikowi pisanie
higienicznie.
> ż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.
Taka jest idea naszego designu, że makra piszą specjaliści, a używają
ich "zwykli programiści", którzy nie wiedzą nawet że to makro.
> 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.
Tak i to jest pomysł, który musimy rozważyć. Bo w tej chwili makra występujące
w quotowaniach nie są expandowane. Gdyby były, to niehigieniczność
nie przedostawałaby się dalej. Tylko czy to nie spowoduje jakichś innych
problemów?
>
> (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.)
Dirty tricks. :)
>
> 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.
Z taką funkcją niejawnie dostępną to my też tak musimy zorobić. Już teraz
makro niejawnie ma dostępną zmienną "_N_ctx" czyli kontekst typowania.
Hmm, między liniami odczytuję algorytm taki, że zmienne kolorowane są
w obrębie makra, a nie quotowania:
<[ def x = 5; ]>
<[ x ]>
jest poprawne, jeśli użyte w tym samym makrze.
Myśleliśmy o tym, ale zrezygnowaliśmy, bo i tak jak ktoś chce używać
zewnętrznej funkcji do generowania kodu, to musi się z nią komunikować za
pomocą generowanych ręcznie symboli. Ale byc może to jest dobry pomysł.
Kamil
More information about the devel-pl
mailing list