Tworzenie większych programów

0

witam
mam problem ze stworzeniem jakiegokolwiek większego projektu
mam zaczęte niezliczoną ilość projektów jednak jeszcze żadnego większego nie skończyłem
po jakimś tygodniu pracy nad czymś poważniejszym, boję się cokolwiek zmienić w kodzie, żeby wszystkiego nie zepsuć
praktycznie po każdej linii dopisanego kodu tworzę kopię całego projektu a do samej zmiany przymierzam się kilka dni :|
w końcu po kilku tygodniach całkiem rezygnuję z projektu :/
zauważyłem to ostatnio u siebie chociaż robię tak już bardzo długo i to siedzi jakoś głębiej w mojej psychice ...
czy ktoś z was miał może kiedyś taki problem ? jak sobie z tym poradzić ? [???]

0

A w czym piszesz?

0

jak powiesz w czym piszesz będzie łatwiej doradzać zapewne więc powiedz=]

Ja jak czuję że kawałki kodu zbyt puchną to siekam tematycznie do różnych modułów, osobno daje moduł z typami danych, osobno z metodami od jakiejś rzeczy.
Jak jakaś metoda jest tak przydługa że trudno ją kontrolować przy zmienianiu to wysiepuje z niej mniejsze kawałki do innych metodek i je wywołuje ( dodatkowy plus to to że można użyć poleceń do wyskakiwania z bloku kodu zamiast stawiać las IF'ów =D ).
Żeby nie pomotać się z danymi wsadzam je do osobnego modułu gdzie siedzą tylko zmienne i stałe, i w tym module grzebią sobie inne.
Jak przyuważę że wiele razy robię podobną czynność to gmeram do niej uniwersalną metodkę

w taki sposób dość wygodnie robię sobie w Delphi ( w Cku bym chyba zemrzał przy takim czymś =D )
w C# tego robić nie muszę ( jeszcze ) bo prawie wszystko co na razie mi jest potrzebne jest w klasach .NET'a =D

0

w czym bym nie pisał
pisałem w pascalu, c, php, delphi, c++, a teraz c# i zmiana języka nic nie zmieniła

0
takisobieprogramista napisał(a)

w czym bym nie pisał
pisałem w pascalu, c, php, delphi, c++, a teraz c# i zmiana języka nic nie zmieniła

A co piszesz?

0

poematy i limeryki ale co to ma do tematu ? O_o

0

Warto zainteresować się SVNem jakimś, wtedy strach, że zmiana jakieś linijki kodu coś zepsuje odchodzi w zapomnienie, zawsze można przywrócić projekt z danej daty, z któej wrzuciło się wersję na serwer.
Druga rzecz to przed rozpoczęciem projektu dokładne go rozplanowanie, całe umle i te sprawy, sporo mogą ułatwić. Schemat działania robisz sobie osobno, na papierze niejako, ewentualne zmiany wprowadzasz i jak uznajesz, że projekt powinien tak wyglądać, to dopiero łapiesz się kodu wg projektu. Wtedy już wiesz, jak co ma wyglądać, jak działać, kwestia obrócenia tego w kod nie powinna być taka straszna, jesli ładnie się kod pisze ;)

0

Pisz testy. Wtedy po każdej niewielkiej zmianie zapuszczasz testy i sprawdzasz, czy się coś nie sypie.

Ważne jest też na bieżąco utrzymywanie porządku i dobrej struktury projektu.
Kilka zasad:

  1. Staraj się, aby każdy moduł / metoda miała dobrze zdefiniowane jedno zadanie (moduł - ogólne, metoda - dosyć szczegółowe).
  2. Staraj się, aby każda klasa reprezentowała jedno pojęcie.
  3. Pisz komentarze do klas/metod. Jeśli masz problem z napisaniem krótkiego komentarza - sygnalizuje to, że ze strukturą projektu jest coś źle i sam nie bardzo wiesz, "co to robi i jak to opisać". Tym bardziej nie będziesz wiedział za miesiąc.
  4. Jeżeli 2 moduły nawzajem siebie potrzebują, to trzeba to b. mocno uzasadnić.
  5. Jeżeli wszystko zależy od wszystkiego w projekcie, to jest bardzo źle.
  6. Jeśli się da, grupuj moduły/klasy w warstwy. Niższe warstwy nie powinny odwoływać się do wyższych.
  7. Używaj interfejsów. Dokładnie je dokumentuj.
  8. Jeśli musisz pisać komentarze wewnątrz kodu metod/funkcji, aby stał się zrozumiały - niedobrze. Komentarze w kodzie powinny odpowiadać na pytanie "po co to się robi?", a nie "co to robi?".

To takich kilka rad, co mi się nasunęło - można listę jeszcze wydłużyć. Oczywiście nie należy trzymać się tego kurczowo,
ale jak się notorycznie łamie którąś zasadę, to trzeba się temu lepiej przyjrzeć.

// A, jeszcze jedno: jeśli nie kończysz projektu, bo masz za małą motywację - pokaż projekt komuś.
Znalezienie pierwszych użytkowników własnych programów daje b. silną motywację.

0

no wreszcie jakaś normalna odpowiedź :)
w tym rzecz że zawsze planuję projekt a mimo to w czasie jego wykonywania zawsze wpadnie jakaś rzecz do przeinaczenia która zazwyczaj wymaga przebudowania połowy kodu
sugerujesz żeby przed rozpoczęciem przysiąść do planowania dzień dłużej czy takie zmiany po prostu są nieuniknione ?

0

dzięki za te rady, właśnie najwięcej czasu po jakiejś zmianie zajmuje mi rozstrzygnięcie czemu ta klasa stała się zależna od innej i jak zrobić żeby nie była

jak pisałem odpowiedź to jeszcze postu Krolika nie było, niemniej jednak pytanie z powyższego posta aktualne

0

:) Zmiany są nieuniknione. Jeśli nie ma zmian, to możliwe, że:

  1. idziesz na łatwiznę
  2. robisz nikomu niepotrzebny projekt

Co do zmian - używaj SVN/CVS oraz narzędzi do refactoringu. SVN ochroni przed tym, że coś totalnie rozwalisz, a IDE pozwoli Ci zaoszczędzić czas przy podstawowych przeróbkach (np. rozbicie jednej klasy na 2, czy przerzucenie klasy w inne miejsce). Na końcu testy powiedzą Ci ile, czego i gdzie się sypie. Nieraz miałem tak, że musiaem wywracać do góry nogami 1/3 projektu na 40 tys. linii i dało radę. Nie ma cudów, jeśli projekt ma być dobry, takie zmiany, bądź nawet przepisanie na nowo pewnych fragmentów jest normalką.

Ważne jest też, że jak dostrzeżesz konieczność zmian w projekcie, wprowadzaj je dosyć szybko. Nie zwlekaj i nie rozwijaj złego projektu, bo tylko dodajesz sobie roboty (zwykle kodu przybywa z czasem).

Niezłym mykiem jest też oznaczanie pewnych części kodu jako Deprecated, jeśli Twój jezyk na to pozwala.
Ja robię często właśnie tak, że zostawiam stary kod, ale go oznaczam w ten sposób - dzięki temu nie ryzykuję aż tak bardzo uszkodzenia projektu, ale z drugiej strony mam nadal porządek, bo kompilator/IDE może mi b. szybko pokazać, gdzie jest kod przeznaczony do usunięcia (i wszystkie odwołania do niego).

0
takisobieprogramista napisał(a)

w tym rzecz że zawsze planuję projekt a mimo to w czasie jego wykonywania zawsze wpadnie jakaś rzecz do przeinaczenia która zazwyczaj wymaga przebudowania połowy kodu

To znaczy, że masz źle rozplanowane moduły/klasy. Też przez to przechodziłem :p

Generalnie chodzi o to aby między modułami było jak najmniej zależności. Niech moduły/klasy komunikują się możliwie prostym interfejsem.

Z czasem co raz lepiej będzie ci to wychodzić ;) Posiedzenie 1 dzień dłużej nad schematem jest dobrym pomysłem. Jak dojdziesz do mistrzostwa to klepanie kodu będzie już tylko formalnością, gdyż cała robota zostanie wykonana wcześniej.

Jak zrobisz np. diagram UML, to warto na 4p zapytać co by tam można ulepszyć :d

0

Dodam jeszcze, że ważne też, żeby zależności między modułami były JAWNE i najlepiej słabe - np. bardzo silna jest zależnosć dziedziczenia, nieco słabsza zależność od implementacji, a najlepsza i najsłabsza - zależność od interfejsu. Jeśli masz dobrze udokumentowane co od czego zależy i w jaki sposób, to takie coś łatwiej ogarnąć i przerabiać.

0

Ja dodam, że w językach w których spaghetti code jest czymś naturalnym (php ;)) warto używać frameworku, który wymusi stosowanie struktury MVC. Polecam :)

0

Teraz będę kłamał.

  1. Pisz w języku umożliwiającym bottom-up programming (leksykalny dialekt Lispu, Haskell, Smalltalk, ostatecznie Ruby, poniżej będę się głównie do Lispu odnosił)
  2. Nie planuj całej architektury na początku, napisz funkcje wykonujące to, co na pewno będzie potrzebne (na przykład pisząc klienta IRC na pewno będziesz potrzebował kodu do komunikacji z serwerem, do parsowania wiadomości, do pobierania wejścia...), potem w dobrym języku można to wszystko łatwo posklejać do kupy (na przykład za pomocą makr). Zaletą takiego systemu jest możliwość debugowania małych funkcji pojedynczo, a gdy ma się już pewność, że dana funkcja działa dobrze, można ją zostawić w spokoju. Unit testy są w takim modelu zbędne - po co testować coś, co my już przetestowaliśmy? Optymalnym rozwiązaniem jest jedno zadanie = jedna funkcja. Jeżeli funkcja ma więcej niż 20 linii, to albo robi ona więcej niż jedno zadanie, albo masz kiepski styl i używasz za mało abstrakcji syntaktycznych i utilsów (polecam lekturę książki On Lisp Grahama).
  3. Nie przejmuj się zbytnio komentarzami - jeżeli z kodu nie wynika od razu, co się dzieje, to znaczy, że albo jesteś kiepskim programistą, albo za używasz za mało abstrakcji syntaktycznych (makra!)
  4. Kontrola wersji jest akurat całkiem niezłym pomysłem.
  5. Skoro już o tym mowa, używaj dobrego IDE - np. Emacs może się równać jedynie z MSVS. W sumie możesz używać nawet vima, pod warunkiem, że jesteś unix console zealot - potrafisz cytować strony mana seda, grepa i wszystkich coreutils wyrwany ze snu po imprezie. Jednakże dla ludzi, którzy jeszcze nie wiedzą, czego chcą od środowiska, vim to męka.
  6. Jak już zostało wspomniane, dobre też jest czasami oparcie się na bibliotekach wymuszających konkretny styl - zwykle ten styl jest wystarczająco dobry, skoro opiera się na nim cała biblioteka. Czasem jednak warto odrzucić odgórnie narzucone ograniczenia i np. stwierdzić, że design patterns by GoF to nie jest jakiś ogólny, wypracowany przez całą społeczność informatyczną standard, tylko zbiór hacków omijających ograniczenia nakładane przez języki B&D.
  7. Interfejsy i wirtualne modele klas to nie są narzędzia ułatwiające życie programisty, tylko próba obejścia ograniczeń nakladanych przez design języka.
0

Dodałbym tylko od siebie, że jeżeli masz problem z robieniem dużych projektów, to bezwzględnie musisz zacząć robić małe. Twój problem to problem wielu programistów i nazywa się on "końca nie widać". :)

Moim zdaniem jedyny na to sposób, to robienie małych klocków. A gdy będziesz miał już ich wystarczająco dużo, to okaże się, że Twój "duży projekt" jest tak naprawdę malutkim programikiem z dość banalnym kodem.
Właściwie całe to programowanie, to działalność wysoce przyrostowa. Na efektach poprzednich prac buduje się kolejne prace. Żeby jednak to ciągnąć trzeba być naprawdę zadowolonym z tych małych klocków. To daje siłę na budowanie kolejnych - równie małych.
Weź pod uwagę jedną rzecz. Działanie każdego programu da się opisać językiem naturalnym w dwóch, trzech zdaniach. Często jest to bardzo wysoki poziom abstrakcji, ale nadal są to tylko 2-3 zdania. Kiedy zdałem sobie sprawę, że mogę stworzyć dosłownie każdy program i zmieścić go w 5-10 linijkach kodu, a każdy klocek z którego się składa również mogę zmieścić w kolejnych kilku wierszach - wszystko stało się łatwiejsze. A duże projekty nagle stają się małe.

0
  1. svn
  2. kiss
  3. dry
0
dodekam napisał(a)
  1. Nie przejmuj się zbytnio komentarzami - jeżeli z kodu nie wynika od razu, co się dzieje, to znaczy, że albo jesteś kiepskim programistą, albo za używasz za mało abstrakcji syntaktycznych (makra!)

Może inaczej - zamiast komentarzy wyjaśniających JAK działa dany kod, pisać CO robi dany kod.
A makra są tylko brzydkim sposobem na ominięcie niedostatków języka. Czasami nie da się ich niestety uniknąć.
A do DSLi spokojnie starczy osobny parser.
W Common Lispie do którego się odnosisz makrą są wręcz katastrofą.

  1. Interfejsy i wirtualne modele klas to nie są narzędzia ułatwiające życie programisty, tylko próba obejścia ograniczeń nakladanych przez design języka.

Wirtualne modele klas - chodzi ci o UML? W większych projektach bez takiego modelu trudno zapewnić sensowne rozdzielenie projektu na kilkanaście niezależnych prac zapewniając jednocześnie późniejszą współpracę.

Interfejs z kolei jest bardzo przydatną ideą; haskellowe klasy Enumerable itd. pełnią generalnie tą sama rolę co interfejsy w takim C++ czy C#.

0

makra w CL katastrofą? zazdrościsz, bo Haskell nie ma? :)
w 7. chodzi mówiąc "wirtualne modele klas" chodzi mi o coś, co c++ nazywa klasami czysto wirtualnymi

0
dodekam napisał(a)

makra w CL katastrofą? zazdrościsz, bo Haskell nie ma? :)

Ma, http://www.haskell.org/haskellwiki/Template_Haskell
Zresztą, Haskell makr jako "łaty" potrzebuje w bardzo niewielu miejscach - właściwie tylko printf i rekordy.

A makrą są katastrofą - cały CL powinien być obiektowy, w praktyce nie jest prawie w ogóle (coerce - dlaczego to jest k*rwa funkcja???).
Właściwie starczyłoby jedno słowo: setf.

(mapcar #'setf l1 l2)

Niemożliwe.. tak samo z makrami pochodnymi.
Jedyne miejsca w CL gdzie makra mają naprawdę sens to: loop + definicje (funkcji, klas, itd) + jakieś formy with-slots itd.
Każdy inny przypadek to po prostu błąd. Tych błędów jest tyle że całość można nazwać tylko jednym słowem - katastrofa.

w 7. chodzi mówiąc "wirtualne modele klas" chodzi mi o coś, co c++ nazywa klasami czysto wirtualnymi

No generalnie to klasy abstrakcyjne pełnią rolę interfejsów, praktycznie minimalna różnica.

0
pompom napisał(a)
dodekam napisał(a)

makra w CL katastrofą? zazdrościsz, bo Haskell nie ma? :)

Ma, http://www.haskell.org/haskellwiki/Template_Haskell
Zresztą, Haskell makr jako "łaty" potrzebuje w bardzo niewielu miejscach - właściwie tylko printf i rekordy.

W CL makra nie pelnia roli lat, o czym powinienes dobrze wiedziec. CL tez nie potrzebuje makr - z makrami jest po prostu duzo wygodniej

pompom napisał(a)

A makrą są katastrofą - cały CL powinien być obiektowy, w praktyce nie jest prawie w ogóle (coerce - dlaczego to jest k*rwa funkcja???).
Właściwie starczyłoby jedno słowo: setf.

(mapcar #'setf l1 l2)

Niemożliwe.. tak samo z makrami pochodnymi.

a od kiedy obiektowosc ma zwiazek z makrami? racja, CL to nie jest akceptowalny Lisp, wiele rzeczy mozna by poprawic, szczegolnie obiektowosc.
twoj przyklad nie tylko jest niemozliwy, ale tez bezsensowny - ten kod mialby sens, gdyby zamiast mapcar bylo np. funcall. poza tym, jak wyobrazasz sobie realizacje takiego czegos? setq to nie bez powodu forma specjalna - tego nie da sie zrobic jako funkcja - sprobuj napisac funkcje, ktora pobiera symbol i ustawia powiazana z nim wartosc 0 - gwarantuje, ze ci sie nie uda.

pompom napisał(a)

Jedyne miejsca w CL gdzie makra mają naprawdę sens to: loop + definicje (funkcji, klas, itd) + jakieś formy with-slots itd.
Każdy inny przypadek to po prostu błąd. Tych błędów jest tyle że całość można nazwać tylko jednym słowem - katastrofa.

Och, f., wielce oswiecony specjalista od makr, ktory jako jedyny wie kiedy mozna ich uzywac... rzeczywiscie, makra nie sa potrzebne, bo prawie zaden jezyk ich nie ma - jednakze pisanie z ich wykorzystaniem jest znacznie wygodniejsze i elegantsze niz bez. jezeli ci sie to nie podoba - nie pisz, zwaz jednak, ze niektorzy nie widza tych wad, ktore znajdujesz ty.

pompom napisał(a)

w 7. chodzi mówiąc "wirtualne modele klas" chodzi mi o coś, co c++ nazywa klasami czysto wirtualnymi

No generalnie to klasy abstrakcyjne pełnią rolę interfejsów, praktycznie minimalna różnica.

wiem, ze pelni taka role, ale nazywa sie inaczej, a chcialem moja wypowiedzia objac wszystkie mozliwosci

0

Możecie sobie o swoich najulubieńszych-cudownych językach pogadać na privie?

@down
Po części deusie, to offtopa możesz przenieść do wątku "LISP - toksyczny związek, miłość i nienawiść jednocześnie"
Bo ja tego zrobić nie mogę. A mojego posta po prostu wywalić ;)
@down2: e tam, ja się co chwilę dowiaduję, że Coyote ma coś, czego nie znałem ;)

0

Wiesz Ranides, po części masz rację. Dobrze by było jednak abyś coś ad meritum też napisał. Co do Dodka zaś - krytyka mu się należy, pisze bzdury. Powoływanie się na On Lisp i ostatnia wypowiedź to hipokryzja.

Makra w CL są faktycznie nadużywane a obecny design języka tragiczny, potencjał to zupełnie inna sprawa. Najsłynniejszy (i jedyny znany) program pisany w CLu - Maxima - jest co najmniej spaprany dokumentnie, kod jest okropny. Dodek, chcesz ludzi na CL nawracać to stosuj sensowne argumenty, rozwijaj projekty, które zwrócą uwagę ludzi...

Konkretami niech się pompom zajmie, ja zapas cierpliwości na ten rok względem Lispu już wyczerpałem naprawiając istniejące od wielu miesięcy bugi w SBCL-u pod Windowsa i portowaniu bibliotek na aplikacje nie-cygwinowe.

@Ranides, jako mod C/C++ doskonale wiesz, że Coyote nie posiada takiej funkcjonalności...

0
dodekam napisał(a)

W CL makra nie pelnia roli lat, o czym powinienes dobrze wiedziec. CL tez nie potrzebuje makr - z makrami jest po prostu duzo wygodniej

Nigdy nie próbowałeś wykonać (map 'nil #'push l1 l2)? Raczej chciałeś, w czymkolwiek bardziej skomplikowanym potrzeba takiego kodu pojawia się dość szybko.

a od kiedy obiektowosc ma zwiazek z makrami? racja, CL to nie jest akceptowalny Lisp, wiele rzeczy mozna by poprawic, szczegolnie obiektowosc.
twoj przyklad nie tylko jest niemozliwy, ale tez bezsensowny - ten kod mialby sens, gdyby zamiast mapcar bylo np. funcall. poza tym, jak wyobrazasz sobie realizacje takiego czegos? setq to nie bez powodu forma specjalna - tego nie da sie zrobic jako funkcja - sprobuj napisac funkcje, ktora pobiera symbol i ustawia powiazana z nim wartosc 0 - gwarantuje, ze ci sie nie uda.

Tam gdzie było miejsce na rozwiązanie obiektowe zostały użyte makra.
Co do tego setf:

(defvar +NIL (let ((a (gensym))) (cons a . a)))
;;nil powinien być consem! Mała, ale denerwująca często rzecz, źródło tysiąca niepotrzebnych (when a (car a)) itd.
;;ale zmiana nila wymaga (żeby np. push zawsze działał) zmiany m.in. mechanizmu rozpoznawania klasy obiektu
;;(to wszystko da się zrobić wykorzystując MOP)
;;starczy pisać funkcyjnie (dołączać element zamiast modyfikować listę) i da się pisać bez tego 

(defun @ (fn &rest arguments)
  (let* ((arguments (copy-list arguments))
	 (last (last arguments)))
    (if arguments
	(lambda (&rest args)
	  (setf (cdr last) args)
	  (apply fn arguments))
	fn)))

(defun |.| (f g)
  (lambda (&rest args) (funcall f (apply g args))))

(defgeneric := (a b c))

(defmethod := ((b (eql 'car)) (a cons) c)
  (prog2 (rplaca a c) a))

(defmethod := ((b (eql 'cdr)) (a cons) c)
  (prog2 (rplacd a c) a))

(defgeneric +push (a b))

(defmethod +push ((a list) b)
  (let ((x (car a)))
    (:= 'car a b)
    (:= 'cdr a (cons x (cdr a)))))
CL-USER> (mapcar (@ #':= 'car) '((1 2 3) (4 5 6 7) (1 4 92)) '(234 64375 53))
((234 2 3) (64375 5 6 7) (53 4 92))

Problem jest jeden - z symbolami. Na podstawie obecnego systemu nie da się tego rozwiazać, nie jest to jednak zbyt dużym problemem - zmiana wartości symbolu jest niesamowicie rzadką operacją i można (w takim 'trochę lepszym lispie') spokojnie zostawić do tego celu setq; dodatkowym argumentem jest bezsensowność form funkcyjnych w tym wypadku (np. (mapcar #'setq ...)).

W najbardziej pierwotnym lispie liczby były reprezentowane jako listy (zbiory); jedną z konsekwencji takiej reprezentacji była 'referencja' - liczby były przekazywane w taki sam sposób jak obecnie np. cons.
Mam na myśli referencje prawie dosłownie jak z C++ - tak, że !(eq a b) gdzie a i b są równe 23 (to jest zgodne nawet z obecnym standardem).

Podsumowując: dzięki jednej, bardzo małej zmianie (pusta lista jest jednocześnie consem), w dodatku kompatybilnej z istniejącym kodem (w większości) możemy pozbyć się prawie wszystkich makrowych setterów; incf/decf będzie musiał zostać makrem, ale brak funkcyjności tych dwóch makr nie jest akurat żadnym problemem.
Ujednolicenie całości wymagałoby zmian na bardziej podstawowym poziomie, nie jest to jednak warte zerwania kompatybilności z istniejącym kodem, dałoby się z czymś takim żyć.

Och, f., wielce oswiecony specjalista od makr, ktory jako jedyny wie kiedy mozna ich uzywac... rzeczywiscie, makra nie sa potrzebne, bo prawie zaden jezyk ich nie ma - jednakze pisanie z ich wykorzystaniem jest znacznie wygodniejsze i elegantsze niz bez. jezeli ci sie to nie podoba - nie pisz, zwaz jednak, ze niektorzy nie widza tych wad, ktore znajdujesz ty.

Uuu, nieładnie - powołujesz się na 'autorytet zbiorowy', trafniej nazywany 'instynktem stadnym' :). Gratulacje, argument zbił mnie z nóg.

Makra przydatne owszem, są, ale nie jako 'zapchajdziura' używana jako remedium na każdy możliwy problem wynikający z niespójnej podstawy (czy raczej podstaw) języka.

Podsumowując podsumowanie :) - prowizorka goni prowizorkę, czego wynikiem jes wielki burdel.

wiem, ze pelni taka role, ale nazywa sie inaczej, a chcialem moja wypowiedzia objac wszystkie mozliwosci

Niech będzie, w porządku.
Mógłbyś w takim razie czymś umotywować swoje zdanie? Klasy abstrakcyjne są używane równie dobrze w C++ jak i w Common Lispie. W tym pierwszym o wiele częściej, owszem - głównie dla ominięcia wad prymitywnego OO, w przypadkach w których czysto koncepcyjnie nie ma to większego sensu.
Jako 'typ zbiorowy' ew. 'informacja o właściwościach' już ma.
W CL-u klasa abstrakcyjna to po prostu klasa bez żadnych pól.
I po co było tak generalizować? :)

P.S. I tak choćby przez brak leniwości i pattern matchingu CL przegrywa na starcie.

0
pompom napisał(a)

Nigdy nie próbowałeś wykonać (map 'nil #'push l1 l2)? Raczej chciałeś, w czymkolwiek bardziej skomplikowanym potrzeba takiego kodu pojawia się dość szybko.

Tak się składa, że nigdy. push przyjmuje jako argument miejsce, co oznacza, że równie dobrze możesz wrzucać poprzez symbol, jak i na przykład akcesor slotu czy aref/svref. Możesz jednak zrobić coś takiego (makro absolutnie zbędne, bo jak dla mnie to tego problemu nie ma - nigdy tego nie potrzebowałem, ale naklepałem takie coś na poczekaniu):

CL-USER> (defmacro mmap (op l1 l2)
	   `(prog1 nil
		 ,@(mapcar (lambda (&rest args) 
			   (cons op args))
			 l1 l2)))
CL-USER> (macroexpand-1 '(mmap push (1 2 3) (a b c)))
(PROG1 NIL (PUSH 1 A) (PUSH 2 B) (PUSH 3 C))
pompom napisał(a)

Tam gdzie było miejsce na rozwiązanie obiektowe zostały użyte makra.

Makra zostały użyte, bo to sposób naturalniejszy dla Lispu i, wybacz, moim zdaniem nieco wygodniejszy. Twój kod sprowadza się do czegoś w stylu (let ((l (list 1 2 3))) (:= 'car l 'foo)) - dla mnie to jest mniej czytelne od analogicznego (setf (car l) 'foo). Siłą Lispu jest fakt, że MOŻNA tak to zrobić - możesz sobie takie coś narzucić i korzystać z tego w całym projekcie. Pytanie tylko, czy jest to Lisp-way.

Co do tego setf:

A widziałeś get-setf-expander?

Problem jest jeden - z symbolami. Na podstawie obecnego systemu nie da się tego rozwiazać, nie jest to jednak zbyt dużym problemem - zmiana wartości symbolu jest niesamowicie rzadką operacją i można (w takim 'trochę lepszym lispie') spokojnie zostawić do tego celu setq; dodatkowym argumentem jest bezsensowność form funkcyjnych w tym wypadku (np. (mapcar #'setq ...)).

Efekty uboczne w ogóle nie są zbyt częste, a wymaganie, by były one zrobione funkcyjnie jest nieco na wyrost - można łatwo wpaść w zbędne komplikacje.

Podsumowując: dzięki jednej, bardzo małej zmianie (pusta lista jest jednocześnie consem), w dodatku kompatybilnej z istniejącym kodem (w większości) możemy pozbyć się prawie wszystkich makrowych setterów; incf/decf będzie musiał zostać makrem, ale brak funkcyjności tych dwóch makr nie jest akurat żadnym problemem.

Oczywiście, że dałoby się - pytanie tylko po co. setf jest nieco ograniczone, ale wygodne, nigdy nie próbowałem mapować makrami ani operatorami specjalnymi, bo nie potrzebowałem tego.

Uuu, nieładnie - powołujesz się na 'autorytet zbiorowy', trafniej nazywany 'instynktem stadnym' :). Gratulacje, argument zbił mnie z nóg.

Sugerujesz mi argumentum ad auditorem? Chciałem tylko zauważyć, że Twoja opinia na temat pchania wszędzie makr niekoniecznie musi być prawdą objawioną. To prawda, da się bez makr całkowicie obejść, tylko po co? Makra to Lisp-way i stosowane poprawnie zdecydowanie ułatwiają życie.

Makra przydatne owszem, są, ale nie jako 'zapchajdziura' używana jako remedium na każdy możliwy problem wynikający z niespójnej podstawy (czy raczej podstaw) języka.

Podsumowując podsumowanie :) - prowizorka goni prowizorkę, czego wynikiem jes wielki burdel.

O designie CL można czasami pomyśleć, że jest sumą błędów, których można było uniknąć. Moje wypowiedzi jednak nie odnosiły się do CL, a do Lispu samego w sobie. Jak powiedział Steve Yegge chyba, No Lisp is acceptable Lisp.

Mógłbyś w takim razie czymś umotywować swoje zdanie? Klasy abstrakcyjne są używane równie dobrze w C++ jak i w Common Lispie. W tym pierwszym o wiele częściej, owszem - głównie dla ominięcia wad prymitywnego OO, w przypadkach w których czysto koncepcyjnie nie ma to większego sensu.
Jako 'typ zbiorowy' ew. 'informacja o właściwościach' już ma.
W CL-u klasa abstrakcyjna to po prostu klasa bez żadnych pól.
I po co było tak generalizować? :)

Miałem na myśli właśnie omijanie tych wad.

P.S. I tak choćby przez brak leniwości i pattern matchingu CL przegrywa na starcie.

I tam, bez leniwości da się żyć, czego przykładem jest fakt, iż zdecydowana większość programistów nie wie w ogóle, co to jest. Równie dobrze mógłbym powiedzieć, że Haskell nie ma składni z S-wyrażeń i to też nie byłby merytoryczny argument.
A pattern matching akurat można dorobić...

Ranides: na privie prowadzimy dużo ostrzejsze flejmy, merytoryczna dyskusja lepiej wychodzi na zimno :)

deus: w każdym języku można tworzyć fatalny kod, w niektórych nawet są do tego specjalne konkursy.

PS: częstotliwość setfów w moim najnowszym projekcie:

~/programowanie/projekty/cellural $ cat *.lisp | wc -l; cat *.lisp | grep setf | wc -l
224
4

@deus: nikt ci nie powie, że maxima jest flagowym lispowym projektem open-source.

0

deus: w każdym języku można tworzyć fatalny kod, w niektórych nawet są do tego specjalne konkursy.

Tak, były takie dwa konkursy, które nazywały się standaryzacją Lispu i flagowym lispowym projektem open-source.

0

@pom

Właściwie starczyłoby jedno słowo: setf.
(mapcar #'setf l1 l2)

Zmiennych leksykalnych zmienić się nie da, w końcu zostały już dawno
zoptymalizowane do rejestrów, niektóre setf-expandery w namespace CL
mogą być zdefiniowane jako makra, ale ogólnie nie ma problemu:

(defvar *x* 'meow)

(defun fsetf (name)
  (let ((fn (fdefinition `(setf ,name))))
    (lambda (place new-value)
      (funcall fn new-value place))))

(let ((names '(car cdr symbol-value))
      (values (copy-tree `((foo . bar)
                           (baz . 42)
                           *x*)))
      (new-values '(xenu 69 nya)))
  (mapc #'funcall (mapcar #'fsetf names) values new-values)
  (values values *x*))
===>
((XENU . BAR) (BAZ . 69) *X*)
NYA

Jedyne miejsca w CL gdzie makra mają naprawdę sens to: loop +
definicje (funkcji, klas, itd) + jakieś formy with-slots itd.

Makra to m.in. sposób implementacji CLOS-a, systemu obiektowego w CL.
Wszystko da się osiągnąć przenośnie, może poza funcallable-instance.

P.S. I tak choćby przez brak leniwości i pattern matchingu CL przegrywa na starcie.

Pattern matching nie sprawia problemów, od czego są makra :)

Leniwość można trywialnie zaimplementować code walkerem (np. cl-walker) sposobem znanym z SICP - każde wywołanie funkcji do thunka. Brakuje tylko standardowej biblioteki :) Gorzej ma się sprawa gdy ma to działać szybciej niż każde-wywołanie-funkcji-do-thunka :)

W ogóle co takiego fajnego w leniwości poza strumieniami? Tak czy siak SERIES całkiem nieźle implementuje leniwe sekwencje.

@dodek

CL to nie jest akceptowalny Lisp, wiele rzeczy mozna by poprawic,
szczegolnie obiektowosc.

Co nie tak z obiektowością? Trudno, żeby każdy typ danych był obiektem,
trudno zrobić dynamicznie typowany język z fixnum jako obiekt mieszczący
się w rejestrze.

/me z nosem w manualu do Qi :)

-- Lisp Jihad

0

Jako programista C++ powiem tak: za kazde napisane makro powinna byc kara tydzien o chlebie i wodzie w ciemnicy.

0
EgonOlsen napisał(a)

Jako programista C++ powiem tak: za kazde napisane makro powinna byc kara tydzien o chlebie i wodzie w ciemnicy.

Haha
Makra Lispu to nie są badziewne podstawieniowe makra preprocesora ;)

0
dodekam napisał(a)

Tak się składa, że nigdy. push przyjmuje jako argument miejsce, co oznacza, że równie dobrze możesz wrzucać poprzez symbol, jak i na przykład akcesor slotu czy aref/svref. Możesz jednak zrobić coś takiego (makro absolutnie zbędne, bo jak dla mnie to tego problemu nie ma - nigdy tego nie potrzebowałem, ale naklepałem takie coś na poczekaniu):

CL-USER> (defmacro mmap (op l1 l2)
	   `(prog1 nil
		 ,@(mapcar (lambda (&rest args) 
			   (cons op args))
			 l1 l2)))
CL-USER> (macroexpand-1 '(mmap push (1 2 3) (a b c)))
(PROG1 NIL (PUSH 1 A) (PUSH 2 B) (PUSH 3 C))

Kolejne makro, jako lekarstwo na problem stworzony przez użycie makra..

dodekam napisał(a)

Makra zostały użyte, bo to sposób naturalniejszy dla Lispu i, wybacz, moim zdaniem nieco wygodniejszy. Twój kod sprowadza się do czegoś w stylu (let ((l (list 1 2 3))) (:= 'car l 'foo)) - dla mnie to jest mniej czytelne od analogicznego (setf (car l) 'foo). Siłą Lispu jest fakt, że MOŻNA tak to zrobić - możesz sobie takie coś narzucić i korzystać z tego w całym projekcie. Pytanie tylko, czy jest to Lisp-way.

Jest mniej czytelne, ale nic nie stoi na przeszkodzie aby stworzyć makro, do używania tylko jako pojedyncze wywołanie działające jak stary setf.
I to jest właśnie jedno z dwóch zastosowań makr - poprawianie czytelności. Żadnych dziur tu nie łatamy - skracamy elastyczną formę ogólną dla jednego zastosowania. Przy obecnym setf ta forma ogólna po prostu nie istnieje.

dodekam napisał(a)

Efekty uboczne w ogóle nie są zbyt częste, a wymaganie, by były one zrobione funkcyjnie jest nieco na wyrost - można łatwo wpaść w zbędne komplikacje.

Wszystko oprócz kilku form specjalnych (set/setq) jest zrobione 'funkcyjnie'. Setf (makro) to rozwiązanie zupełnie innego problemu, braku generycznego settera - metod wtedy gdy powstawał nie było.
~30 lat to jednak dość dużo czasu - to co kiedyś było najlepszym rozwiązaniem dziś jest bezsensowne.

dodekam napisał(a)

Sugerujesz mi argumentum ad auditorem? Chciałem tylko zauważyć, że Twoja opinia na temat pchania wszędzie makr niekoniecznie musi być prawdą objawioną. To prawda, da się bez makr całkowicie obejść, tylko po co? Makra to Lisp-way i stosowane poprawnie zdecydowanie ułatwiają życie.

I m.in. przez to makrowe Lisp-way CL jest martwy (może raczej: w ciągłej agonii) :) Mimo wręcz ogromnej jak na tak mało popularny język reklamy, mimo tak ogromnego mitu, przegrywa z Pascalem (nie: Delphi) w popularności...
(http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html)
Umrze całkowicie chyba dopiero razem ze starą gwardią (założyciele FOSS itd).

dodekam napisał(a)

P.S. I tak choćby przez brak leniwości i pattern matchingu CL przegrywa na starcie.

I tam, bez leniwości da się żyć, czego przykładem jest fakt, iż zdecydowana większość programistów nie wie w ogóle, co to jest. </quote>
Równie dobrze można żyć bez funkcyjności, a jeszcze 5 lat temu można było żyć bez GC.
Język niepopularny nie może zaoferować ogromu bibliotek/itd - może tylko zaoferować ekspresywność.
(w przypadku lispu nie ma opcji odziedziczenia popularności jakiegoś innego języka, jak to zrobił c++, więc to pomijam).

dodekam napisał(a)

A pattern matching akurat można dorobić...

lisp-jihad napisał(a)

Pattern matching nie sprawia problemów, od czego są makra :)

Nie da się. Da się tylko rozszerzyć istniejące specjalizery na equal/equalp/inny, korzystając z ogromnych możliwości MOP. Nie można natomiast zaimplementować guardów:

fun x | x < 1 = ..
fun x | x > 25 = ..
fun x | x > 5 = ..
fun x  = ..

W CLu kolejność definicji nie ma znaczenia - więc jak rozwiazać problem wywołania funkcji w przypadku gdy x = 26?
Zadowalającego rozwiązania nie ma. Jedna możliwość to ręczne podawanie kolejności, druga to dziedziczenie metod specjalizujących (tak, że >25 byłaby synem >5) - trochę przekombinowane.
Akurat pm nie mieści się po prostu w składni, niestety.

A makrem to da się zrobić destructuring-bind, ew jakieś nieznaczne rozszerzenie.

(destructuring-bind ((a b) c) '((1 2) 3) (+ a b c))
=> 6

Trudno to uznać za pełnowartościowe pm..

lisp-jihad napisał(a)

Zmiennych leksykalnych zmienić się nie da, w końcu zostały już dawno
zoptymalizowane do rejestrów, niektóre setf-expandery w namespace CL
mogą być zdefiniowane jako makra, ale ogólnie nie ma problemu:

Po prostu zaimplementowałeś jako funkcja to co ja zrobiłem na metodach. Przy okazji otrzymując pełny polimorfizm, możliwość zastosowania niektórych technik z programowania aspektowego (:after itd.) - ogółem, elastyczność.

lisp-jihad napisał(a)

Makra to m.in. sposób implementacji CLOS-a, systemu obiektowego w CL.
Wszystko da się osiągnąć przenośnie, może poza funcallable-instance.

Jedyne do czego makra są wykorzystane w CLOSie to ułatwienie pisania definicji. Zamiast ręcznie tworzyć instancję klasy metody piszesz od razu jej kod, makro samo doda zwykły schemat. I tyle tych makr.
Z implementacją CLOSa nie mają żadnego związku, CLOS równie dobrze działałby bez makr w ogóle.

lisp-jihad napisał(a)

Leniwość można trywialnie zaimplementować code walkerem (np. cl-walker) sposobem znanym z SICP - każde wywołanie funkcji do thunka. Brakuje tylko standardowej biblioteki :) Gorzej ma się sprawa gdy ma to działać szybciej niż każde-wywołanie-funkcji-do-thunka :)

W ogóle co takiego fajnego w leniwości poza strumieniami? Tak czy siak SERIES całkiem nieźle implementuje leniwe sekwencje.

Nie za bardzo, trzebaby też zmienić każde odwołanie do zmiennej na wywołanie funkcji. Można leniwość zemulować w znacznym zakresie i tyle.
Leniwość zupełnie zmienia sposób pisania, można spokojnie napisać algorytm o złożoności teoretycznie wykładniczej - niepotrzebne rzeczy po prostu w ogóle się nie policzą, nie trzeba o tym w ogóle myśleć. W nieleniwym języku trzeba męczyć się z flagami/itd... czyli po prostu emulować leniwość.

lisp-jihad napisał(a)

Co nie tak z obiektowością? Trudno, żeby każdy typ danych był obiektem,
trudno zrobić dynamicznie typowany język z fixnum jako obiekt mieszczący
się w rejestrze.

W CLu każdy typ danych +JEST+ obiektem... (i to nawet dosłownie ;) Wszystko jest obiektem, nawet zwykła funkcja/metoda ;)
Problem w tym, że większość klas jest nierozszerzalna; takie 'sealed' z C#.
A wymuszenie włożenia obiektu ;) do rejestru wymaga specjalnej deklaracji, którą OBIECUJESZ kompilatorowi że typ będzie taki a taki i że ma to założyć z góry.

EgonOlsen napisał(a)

Jako programista C++ powiem tak: za kazde napisane makro powinna byc kara tydzien o chlebie i wodzie w ciemnicy.

Nie korzystasz z szablonów? Szablony to przecież po prostu podstawieniowe makra z prostym pm ;)</quote>

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