„Paradoks blub” i c ++

37

Czytałem artykuł tutaj: http://www.paulgraham.com/avg.html, a część o „paradoksie blub” była szczególnie interesująca. Jako ktoś, kto głównie koduje w c ++, ale ma kontakt z innymi językami (głównie Haskell), jestem świadomy kilku przydatnych rzeczy w tych językach, które trudno jest powielić w c ++. Pytanie skierowane jest głównie do osób biegle posługujących się zarówno językiem c ++, jak i jakimś innym językiem. Czy istnieje jakaś potężna funkcja lub idiom językowy, którego używasz w języku, który trudno byłoby pojąć lub wdrożyć, gdybyś pisał tylko w języku c ++?

W szczególności ten cytat przykuł moją uwagę:

Dzięki indukcji jedynymi programistami, którzy mogą zobaczyć wszystkie różnice w mocy między różnymi językami, są ci, którzy rozumieją ten najsilniejszy. (Prawdopodobnie to właśnie miał na myśli Eric Raymond o tym, że Lisp uczynił cię lepszym programistą.) Nie możesz ufać opiniom innych, z powodu paradoksu Blub: są zadowoleni z dowolnego języka, którego używają, ponieważ to dyktuje sposób, w jaki myślą o programach.

Jeśli okaże się, że jestem odpowiednikiem programisty „Blub” dzięki użyciu c ++, powstaje następujące pytanie: Czy są jakieś użyteczne koncepcje lub techniki, które napotkałeś w innych językach, które trudno byłoby ci pojąć, gdybyś pisałeś lub „myślałeś” w c ++?

Na przykład paradygmat programowania logicznego widziany w językach takich jak Prolog i Mercury można zaimplementować w c ++ przy użyciu biblioteki castor, ale ostatecznie uważam, że koncepcyjnie myślę w kategoriach kodu Prolog i tłumacząc na odpowiednik c ++ podczas korzystania z tego. W celu poszerzenia mojej wiedzy programistycznej próbuję dowiedzieć się, czy istnieją inne podobne przykłady użytecznych / potężnych idiomów, które są bardziej efektywnie wyrażane w innych językach, o których być może nie jestem świadomy jako programista c ++. Innym przykładem, który przychodzi mi na myśl, jest system makr w lisp, generowanie kodu programu z poziomu programu wydaje się mieć wiele zalet w przypadku niektórych problemów. Wydaje się to trudne do zaimplementowania i przemyślenia z poziomu c ++.

To pytanie nie ma być debatą „c ++ vs lisp” ani żadną debatą typu wojny językowe. Zadanie takiego pytania to jedyny sposób, w jaki mogę dowiedzieć się o rzeczach, o których nie wiem, o których nie wiem.

transfer87
źródło
2
Zgadzam się. Dopóki nie zmieni się to w debatę C ++ kontra Lisp, myślę, że jest tu coś do nauczenia się.
jeffythedragonslayer
@MasonWheeler: there are things that other languages can do that Lisp can't- Mało prawdopodobne, ponieważ Lisp jest ukończony w Turingu . Być może chciałeś powiedzieć, że są pewne rzeczy, które nie są praktyczne w Lisp? Mógłbym powiedzieć to samo o każdym języku programowania.
Robert Harvey,
2
@RobertHarvey: „Wszystkie języki są równie potężne w sensie bycia odpowiednikiem Turinga, ale to nie jest znaczenie słowa, o które dbają programiści. (Nikt nie chce programować maszyny Turinga). Rodzaj, o który dbają programiści, może nie być formalnie możliwe do zdefiniowania, ale jednym ze sposobów wyjaśnienia tego byłoby powiedzenie, że odnosi się do funkcji, które można uzyskać w mniej zaawansowanym języku, pisząc tłumacza dla mocniejszego języka w tym języku. ” - Paul Graham, w przypisie do przedmiotowego słupka trollowego. (Zobacz, co mam na myśli?)
Mason Wheeler,
@Mason Wheeler: (Nie bardzo.)
Robert Harvey,

Odpowiedzi:

16

Odkąd wspominałeś o Haskell:

  1. Dopasowywanie wzorów. Uważam, że dopasowanie wzorca jest o wiele łatwiejsze do czytania i pisania. Zastanów się nad definicją mapy i zastanów się, w jaki sposób zostanie ona zaimplementowana w języku bez dopasowania wzorca.

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
    
  2. System typów. Czasami może to być ból, ale jest niezwykle pomocny. Musisz się z nim zaprogramować, aby naprawdę to zrozumieć i ile błędów wyłapie. Również przejrzystość referencyjna jest cudowna. Po programowaniu w Haskell staje się widoczne tylko przez chwilę, ile błędów jest spowodowanych zarządzaniem stanem w imperatywnym języku.

  3. Ogólnie programowanie funkcjonalne. Używanie map i foldów zamiast iteracji. Rekurencja Chodzi o myślenie na wyższym poziomie.

  4. Leniwa ocena. Znów chodzi o myślenie na wyższym poziomie i umożliwienie systemowi oceny.

  5. Cabal, pakiety i moduły. Posiadanie pakietów Cabala dla mnie jest o wiele wygodniejsze niż znajdowanie kodu źródłowego, pisanie makefile itp. Możliwość importowania tylko niektórych nazw jest znacznie lepsza niż zasadniczo zrzucenie wszystkich plików źródłowych, a następnie skompilowanie.

Joel Burget
źródło
2
Uwaga na temat dopasowania wzorca: nie powiedziałbym, że łatwiej jest pisać w ogóle, ale po przeczytaniu trochę o problemie z wyrażeniem staje się jasne, że rzeczy takie jak instrukcje if i switch, wyliczenia i wzorzec obserwatora są gorszymi implementacjami Algebraicznych Typów Danych + Dopasowywanie wzorów. (I nie zaczynajmy nawet od tego, jak Może sprawia, że ​​wyjątki zerowego wskaźnika
stają się
To, co mówisz, jest prawdą, ale problem z wyrażeniem dotyczy ograniczeń algebraicznych typów danych (i podwójnych ograniczeń standardowego OOP).
Blaisorblade
@hugomg Czy chodziło Ci o wzór gościa zamiast obserwatora?
Sebastian Redl
tak. Zawsze
zmieniam
@hugomg Nie chodzi o posiadanie Maybe(dla C ++ patrz std::optional), chodzi o jawne oznaczenie rzeczy jako opcjonalne / nullable / może.
Deduplicator
7

Zapamiętaj!

Spróbuj napisać w C ++. Nie z C ++ 0x.

Zbyt kłopotliwy? Okej, spróbuj z C ++ 0x.

Sprawdź, czy możesz pokonać tę 4-liniową (lub 5-liniową, cokolwiek: P) wersję kompilacji w D:

auto memoize(alias Fn, T...)(T args) {
    auto key = tuple(args);                               //Key is all the args
    static typeof(Fn(args))[typeof(key)] cache;           //Hashtable!
    return key in cache ? cache[key] : (cache[key] = Fn(args));
}

Aby zadzwonić, wystarczy tylko:

int fib(int n) { return n > 1 ? memoize!(fib)(n - 1) + memoize!(fib)(n - 2) : 1;}
fib(60);

Możesz także wypróbować coś podobnego w schemacie, chociaż jest to nieco wolniejsze, ponieważ dzieje się to w czasie wykonywania i ponieważ wyszukiwanie tutaj jest liniowe zamiast mieszania (i cóż, ponieważ jest to schemat):

(define (memoize f)
    (let ((table (list)))
        (lambda args
            (cdr
                (or (assoc args table)
                    (let ((entry (cons args (apply f args))))
                        (set! table (cons entry table))
                        entry))))))
(define (fib n)
        (if (<= n 1)
            1
            (+ (fib (1- n))
                (fib (- n 2)))))))
(set! fib (memoize fib))
Mehrdad
źródło
1
Więc kochasz APL, w którym możesz pisać cokolwiek w jednym wierszu? Rozmiar nie ma znaczenia!
Bo Persson
@ Bo: Nie korzystałem z APL. Nie jestem pewien, co rozumiesz przez „rozmiar nie ma znaczenia”, ale czy jest coś nie tak z moim kodem, co sprawia, że ​​tak mówisz? I czy jest jakaś zaleta, w jaki sposób zrobiłbyś to w innym języku (jak C ++), którego nie jestem świadomy? (Trochę edytowałem nazwy zmiennych, na wypadek gdybyś miał na myśli to.)
Mehrdad
1
@ Mehrdad - Mój komentarz na temat najbardziej kompaktowego programu nie jest oznaką najlepszego języka programowania. W takim przypadku APL wygrałby bez wysiłku , ponieważ robisz większość rzeczy za pomocą jednego operatora char. Jedynym problemem jest to, że jest nieczytelny.
Bo Persson
@Bo: Tak jak powiedziałem, nie polecałem APL; Nigdy tego nie widziałem. Rozmiar był jednym z kryteriów (choć znaczącym, co widać po wypróbowaniu tego w C ++) ... ale czy coś jest nie tak z tym kodem?
Mehrdad
1
@Matt: Kod memoized do funkcji, ale kod ten może memoize żadnej funkcji. To wcale nie są tak naprawdę równoważne. Jeśli faktycznie spróbujesz napisać taką funkcję wyższego rzędu w C ++ 0x, jest to o wiele bardziej nużące niż w D (chociaż wciąż jest całkiem możliwe ... chociaż nie jest możliwe w C ++ 03).
Mehrdad
5

C ++ to język oparty na wielu algorytmach, co oznacza, że ​​próbuje obsługiwać wiele sposobów myślenia. Czasami funkcja C ++ jest bardziej niezręczna lub mniej płynna niż implementacja innego języka, jak ma to miejsce w przypadku programowania funkcjonalnego.

To powiedziawszy, nie mogę oderwać od głowy natywnej funkcji języka C ++, która robi to, co robi yieldw Pythonie lub JavaScript.

Innym przykładem jest programowanie równoległe . C ++ 0x ma coś do powiedzenia na ten temat, ale obecny standard tego nie robi, a współbieżność jest zupełnie nowym sposobem myślenia.

Ponadto szybki rozwój - nawet programowanie w powłoce - jest czymś, czego nigdy się nie nauczysz, jeśli nigdy nie opuścisz domeny programowania w C ++.

wilhelmtell
źródło
Nie mogę nawet zacząć myśleć, jak trudno byłoby stworzyć generatory w C ++, biorąc pod uwagę C ++ 2003. C ++ 2011 ułatwiłoby to, ale nadal nie jest trywialne. Generatory, pracujące rutynowo z C ++, C # i Pythonem, są najłatwiejszą funkcją, za którą tęsknię najbardziej w C ++ (teraz, gdy C ++ 2011 dodał lambdy).
Wiem, że zostanę za to postrzelony, ale gdybym absolutnie musiał zaimplementować generatory w C ++, musiałbym użyć ... setjmpi longjmp. Nie mam pojęcia, ile to się psuje, ale chyba pierwsze byłyby wyjątki. Teraz, jeśli mi wybaczysz, muszę ponownie przeczytać Modern C ++ Design, aby wymyślić to z mojej głowy.
Mike DeSimone
@Mike DeSimone, czy możesz rozwinąć (zwięźle), w jaki sposób spróbowałbyś rozwiązania z setjmp i longjmp?
1
Koroutyny są izomorficzne dla funktorów.
GManNickG
1
@Xeo, @Mike: xkcd.com/292
Mehrdad
5

Coroutines to niezwykle przydatna funkcja językowa, która stanowi podstawę wielu bardziej namacalnych zalet innych języków w stosunku do C ++. Zasadniczo zapewniają dodatkowe stosy, dzięki czemu funkcje mogą być przerywane i kontynuowane, zapewniając językowi podobne do potoków funkcje, które z łatwością przekazują wyniki operacji przez filtry do innych operacji. To wspaniałe, aw Ruby uważam to za bardzo intuicyjne i eleganckie. Leniwa ocena również się z tym wiąże.

Introspekcja i kompilacja / wykonanie / ocena kodu w czasie wykonywania to cokolwiek, co stanowi potężne funkcje, których brakuje w C ++.

Tony
źródło
Coroutines są dostępne w FreeRTOS ( patrz tutaj ), który jest zaimplementowany w C. Zastanawiam się, co by było potrzebne, aby działały w C ++?
Mike DeSimone
Wspólne procedury są paskudnym włamaniem do emulacji obiektów w C. W C ++ obiekty służą do łączenia kodu i danych. Ale w C nie możesz. Używasz więc stosu rutyny do przechowywania danych, a funkcji rutyny do przechowywania kodu.
MSalters
Coroutines in C ++: crystalclearsoftware.com/soc/coroutine
Ferruccio
1
@Ferruccio: Dzięki za link ... jest też kilka artykułów w Wikipedii. @MSalters: co sprawia, że ​​opisujesz wspólne procedury jako „paskudny hack”? Wydaje mi się to bardzo arbitralną perspektywą. Używanie stosu do przechowywania stanu również za pomocą algorytmów rekurencyjnych - czy one też są hackerskie? FWIW, coroutines i OOP pojawiły się na scenie mniej więcej w tym samym czasie (wczesne lata 60.) ... aby powiedzieć, że hack dla obiektów w C wydaje się dziwny ... Wyobrażam sobie, że kilku programistów C było wówczas zainteresowanych emulacją obiektów, > 15 lat przed C ++.
Tony
4

Po wdrożeniu systemu algebry komputerowej zarówno w Lisp, jak i C ++, mogę powiedzieć, że zadanie było znacznie łatwiejsze w Lisp, mimo że byłem kompletnym nowicjuszem w tym języku. Ta uproszczona natura wszystkich list upraszcza wiele algorytmów. To prawda, że ​​wersja C ++ była zillion razy szybsza. Tak, mogłem przyspieszyć wersję lisp, ale kod nie byłby tak lispy. Na przykład skrypty to kolejna rzecz, która zawsze będzie łatwiejsza, to seplenienie. Chodzi o użycie odpowiedniego narzędzia do pracy.

jeffythedragonslayer
źródło
Jaka była różnica prędkości?
quant_dev
1
@quant_dev: oczywiście wielokrotność zillionów!
Matt Ellen
Nigdy tak naprawdę tego nie mierzyłem, ale miałem wrażenie, że duże litery O są inne. Oryginalnie napisałem wersję C ++ w funkcjonalnym stylu i miała ona również problemy z prędkością, dopóki nie nauczyłem go modyfikować struktury danych zamiast tworzyć nowe, zmienione. Ale to również utrudniło odczytanie kodu ...
jeffythedragonslayer
2

Co mamy na myśli, mówiąc, że jeden język jest „potężniejszy” niż inny? Kiedy mówimy, że język jest „ekspresyjny”? Lub „bogaty”? Myślę, że mamy na myśli, że język zyskuje na sile, gdy jego pole widzenia jest wystarczająco wąskie, aby łatwo i naturalnie opisać problem - tak naprawdę zmiana stanu, prawda? - które jest zgodne z tym poglądem. Jednak ten język jest znacznie mniej wydajny, mniej wyrazisty i mniej przydatny, gdy nasze pole widzenia się poszerzy.

Im bardziej „mocny” i „wyrazisty” jest język, tym bardziej ograniczone jest jego użycie. Może więc „potężny” i „ekspresyjny” to niewłaściwe słowa, których można użyć w przypadku wąskiego narzędzia. Może „odpowiednie” lub „abstrakcyjne” to lepsze słowa na takie rzeczy.

Zacząłem od programowania, pisząc całą masę rzeczy niskiego poziomu: sterowniki urządzeń z ich procedurami przerwania; programy osadzone; kod systemu operacyjnego. Kod był ściśle związany ze sprzętem i napisałem to wszystko w języku asemblera. Nie powiedzielibyśmy, że asembler jest w najmniej abstrakcyjny sposób, a jednak był i jest najpotężniejszym i najbardziej wyrazistym językiem ze wszystkich. Potrafię wyrazić każdy problem w języku asemblera; jest tak potężny, że mogę zrobić wszystko, co mi się podoba z dowolną maszyną.

Całe moje późniejsze rozumienie języka wyższego poziomu zawdzięcza wszystko mojemu doświadczeniu z asemblerem. Wszystko, czego nauczyłem się później, było łatwe, ponieważ, jak widać, wszystko - bez względu na to, jak abstrakcyjne - musi ostatecznie dostosować się do sprzętu.

Możesz zapomnieć o coraz wyższych poziomach abstrakcji - czyli węższych i węższych polach widzenia. Zawsze możesz to odebrać później. Uczenie się w mgnieniu oka to kwestia dni. Moim zdaniem lepiej byłoby uczyć się języka sprzętu 1 , aby zbliżyć się jak najdalej do kości.


1 Być może nie dość niemądry, ale cari cdrbiorę ich nazwy od sprzętu: pierwszy Lisp działał na maszynie, która miała rzeczywisty Rejestr Decrement i rzeczywisty Rejestr Adresowy. Co powiesz na to?

Pete Wilson
źródło
Uważasz, że ten wybór to miecz o podwójnej krawędzi. Wszyscy tego szukamy, ale ma to swoją ciemną stronę i sprawia, że ​​jesteśmy nieszczęśliwi. Lepiej mieć bardzo określony widok świata i określone granice, w których możesz operować. Ludzie są bardzo kreatywnymi stworzeniami i potrafią robić wspaniałe rzeczy za pomocą ograniczonych narzędzi. Podsumowując, mówię, że to nie jest język programowania, ale talent ludzi, którzy potrafią sprawić, że każdy język zaśpiewa!
Czad
„Sugerować to tworzyć; definiować to niszczyć”. Myślenie, że możesz ułatwić życie z innym językiem, jest przyjemne, ale kiedy wykonasz skok, musisz poradzić sobie z brodawkami nowego języka.
Mike DeSimone
2
Powiedziałbym, że angielski jest znacznie potężniejszy i bardziej ekspresyjny niż jakikolwiek język programowania, a jednak granice jego przydatności rozszerzają się każdego dnia, a jego użyteczność jest ogromna. Część siły i ekspresji pochodzi z umiejętności komunikowania się na odpowiednim poziomie abstrakcji i umiejętności wynalezienia nowych poziomów abstrakcji, gdy jest to potrzebne.
molbdnilo
@Mike, więc musisz poradzić sobie z komunikacją z poprzednim językiem w nowym;)
Czad
2

Tablice asocjacyjne

Typowy sposób przetwarzania danych to:

  • odczytanie danych wejściowych i zbudowanie z nich hierarchicznej struktury,
  • tworzenie indeksów dla tej struktury (np. w innej kolejności),
  • tworzenie ich wyciągów (przefiltrowanych części),
  • znalezienie wartości lub grupy wartości (węzeł),
  • przearanżuj strukturę (usuń węzły, dodaj, dołącz, usuń podelementy na podstawie reguły itp.),
  • zeskanuj drzewo i wydrukuj lub zapisz niektóre jego części.

Właściwym narzędziem do tego jest tablica asocjacyjna .

  • Najlepsze wsparcie językowe dla tablic asocjacyjnych, jakie widziałem, to MUMPS , gdzie tablice asocjacyjne to: 1. zawsze sortowane 2. można je utworzyć na dysku (tak zwana baza danych) o tej samej składni. (Efekt uboczny: jest wyjątkowo wydajny jako baza danych, programista ma dostęp do natywnego btree. Najlepszy system NoSQL w historii).
  • Moja druga nagroda to PHP , lubię foreach i łatwą składnię, np. $ A [] = x lub $ a [x] [y] [z] ++ .

Nie podoba mi się składniowa tablica asocjacyjna JavaScript, ponieważ nie mogę utworzyć, powiedzmy [x] [y] [z] = 8 , najpierw muszę utworzyć [x] i [x] [y] .

Okej, w C ++ (i Javie) jest całkiem fajne portfolio klas kontenerów, Map , Multimap , cokolwiek, ale jeśli chcę skanować, muszę zrobić iterator, a kiedy chcę wstawić nowy element głęboki, ja trzeba stworzyć wszystkie wyższe poziomy itp. Niewygodne.

Nie twierdzę, że nie ma użytecznych tablic asocjacyjnych w C ++ (i Javie), ale języki skryptowe bez czcionek (lub nie o ścisłym typowaniu) biją skompilowane, ponieważ są to języki bez czcionek.

Oświadczenie: Nie znam C # i innych języków .NET, AFAIK mają dobrą obsługę tablicy asocjacyjnej.

ern0
źródło
1
Pełne ujawnienie: MUMPS nie jest dla wszystkich. Cytat: Aby dać nieco bardziej „rzeczywisty” przykład horroru, którym jest MUMPS, zacznij od wzięcia udziału w Międzynarodowym Konkursie Obfuscated C Code, szczypce Perla, dwóch kumulujących się środków FORTRAN i SNOBOL oraz niezależnych i nieskoordynowanych wkładach dziesiątki badaczy medycznych i gotowe.
Mike DeSimone
1
W Pythonie możesz użyć wbudowanego dicttypu (np. x = {0: 5, 1: "foo", None: 500e3}Pamiętaj, że nie ma wymogu, aby klucze lub wartości były tego samego typu). Próba zrobienia czegoś podobnego a[x][y][z] = 8jest trudna, ponieważ język musi patrzeć w przyszłość, aby sprawdzić, czy zamierzasz ustawić wartość lub stworzyć inny poziom; samo wyrażenie a[x][y]tego nie mówi.
Mike DeSimone
MUMPS jest pierwotnie językiem podobnym do podstawowego z tablicami asocjacyjnymi (może być bezpośrednio przechowywany na dysku!). Późniejsze wersje zawierają rozszerzenia proceduralne, co czyni go bardzo podobnym do podstawowego PHP. Jeden, który boi się języka Basic i PHP, powinien uważać Świnkę za przerażającą, ale inni nie. Programiści nie. I pamiętaj, że jest to bardzo stary system, wszystkie dziwne rzeczy, takie jak instrukcje jednoliterowe (chociaż możesz użyć pełnych nazw), kolejność oceny LR itp. - a także nieobce rozwiązania - mają tylko jeden cel: optymalizację .
ern0
„Powinniśmy zapomnieć o małej wydajności, powiedzmy w około 97% przypadków: przedwczesna optymalizacja jest źródłem wszelkiego zła. Jednak nie powinniśmy tracić naszych możliwości w tak krytycznych 3%”. - Donald Knuth. To, co opisujesz, brzmi dla mnie jak język starszego typu, który kładzie nacisk na kompatybilność wsteczną i jest w porządku. Osobiście w tych aplikacjach uważam, że łatwość konserwacji jest ważniejsza niż optymalizacja, a język z niealgebraicznymi wyrażeniami i poleceniami jednoliterowymi brzmi nieproduktywnie. Obsługuję klienta, a nie język.
Mike DeSimone
@Mike: bardzo zabawne linki, które opublikowałeś, śmiałem się z ich czytania.
transfer87
2

Nie uczę się Java, C \ C ++, asemblera i Java Script. Używam C ++ do zarabiania na życie.

Chociaż muszę powiedzieć, że bardziej lubię programowanie asemblacyjne i programowanie C. Jest to związane głównie z programowaniem imperatywnym.

Wiem, że paradygmaty programowania są ważne, aby kategoryzować typy danych i dawać abstrakcyjne koncepcje programowania, aby umożliwić wydajne wzorce projektowe i formalizację kodu. Chociaż w pewnym sensie, każdy Paradygmat jest zbiorem wzorców i kolekcji w celu wyodrębnienia podstawowej warstwy sprzętowej, abyś nie musiał myśleć o EAX lub IP wewnętrznie w maszynie.

Moim jedynym problemem jest to, że pozwala to, aby pojęcie i koncepcje działania maszyny zostały przekształcone w ideologię i dwuznaczne twierdzenia o tym, co się dzieje. Ten chleb zawiera wszelkiego rodzaju wspaniałe abstrakty oprócz streszczeń do jakiegoś ideologicznego celu programisty.

Pod koniec dnia lepiej jest mieć jasne podejście i granice dotyczące tego, czym jest procesor i jak działają komputery pod maską. Wszystkim, którym zależy na CPU, jest wykonanie szeregu instrukcji, które przenoszą dane do pamięci i do pamięci do rejestru i wykonują instrukcje. Nie ma pojęcia typu danych ani żadnych wyższych koncepcji programistycznych. To tylko przenosi dane.

Staje się to bardziej skomplikowane, gdy dodasz do miksu paradygmaty programowania, ponieważ nasze spojrzenie na świat jest inne.

Czad
źródło
2

czy jest jakaś potężna funkcja lub idiom językowy, którego używasz w języku, który byłby trudny do wyobrażenia lub wdrożenia, gdybyś pisał tylko w c ++?

Czy są jakieś użyteczne koncepcje lub techniki, które napotkałeś w innych językach, które trudno byłoby ci pojąć, gdybyś pisał lub „myślał” w c ++?

C ++ sprawia, że ​​wiele podejść jest trudnych. Posunąłbym się nawet do stwierdzenia, że ​​większość programowania jest trudna do konceptualizacji, jeśli ograniczysz się do C ++. Oto kilka przykładów problemów, które można łatwiej rozwiązać w sposób utrudniający C ++.

Zarejestruj konwencje przydziału i połączeń

Wiele osób myśli o C ++ jako języku niskiego poziomu, ale tak naprawdę nie jest. Wyodrębniając ważne szczegóły maszyny, C ++ utrudnia konceptualizację praktycznych aspektów, takich jak przydzielanie rejestrów i konwencje wywoływania.

Aby dowiedzieć się więcej o takich koncepcjach, polecam programowanie w asemblerze i zapoznaj się z tym artykułem na temat jakości generowania kodu ARM .

Generowanie kodu w czasie wykonywania

Jeśli znasz tylko C ++, prawdopodobnie myślisz, że szablony są najważniejszym metaprogramowaniem. Nie są. W rzeczywistości są obiektywnie złym narzędziem do metaprogramowania. Każdy program, który manipuluje innym programem, jest metaprogramem, w tym interpreterami, kompilatorami, komputerowymi systemami algebry i dowodami twierdzeń. Przydatne jest do tego generowanie kodu w czasie wykonywania.

Polecam odpalenie implementacji schematu i zabawę, EVALaby dowiedzieć się o ewaluacji śródkolistej.

Manipulowanie drzewami

Drzewa są wszędzie w programowaniu. Podczas parsowania masz abstrakcyjne drzewa składniowe. W kompilatorach masz IR, które są drzewami. W grafice i programowaniu GUI masz drzewa scen.

Ten „śmiesznie prosty parser JSON dla C ++” waży zaledwie 484 LOC, co jest bardzo małe dla C ++. Teraz porównaj to z moim prostym parserem JSON, który waży zaledwie 60 LOC F #. Różnica polega przede wszystkim na tym, że algebraiczne typy danych ML i dopasowywanie wzorców (w tym aktywne wzorce) znacznie ułatwiają manipulowanie drzewami.

Sprawdź także czerwono-czarne drzewa w OCaml .

Czysto funkcjonalne struktury danych

Brak GC w C ++ praktycznie uniemożliwia przyjęcie pewnych przydatnych podejść. Czysto funkcjonalne struktury danych są jednym z takich narzędzi.

Na przykład sprawdź ten 47-liniowy moduł dopasowywania wyrażeń regularnych w OCaml. Zwięzłość wynika głównie z szerokiego zastosowania czysto funkcjonalnych struktur danych. W szczególności korzystanie ze słowników z kluczami, które są zestawami. Jest to naprawdę trudne w C ++, ponieważ wszystkie słowniki i zestawy stdlib są zmienne, ale nie można mutować kluczy słownika lub psujesz kolekcję.

Programowanie logiczne i bufory cofania są innymi praktycznymi przykładami, w których czysto funkcjonalne struktury danych sprawiają, że coś trudnego w C ++ jest naprawdę łatwe w innych językach.

Woła ogon

C ++ nie tylko nie gwarantuje wywołania ogona, ale RAII jest zasadniczo w sprzeczności z tym, ponieważ destruktory przeszkadzają w wywołaniu ogona. Wywołania ogona umożliwiają wykonywanie nieograniczonej liczby wywołań funkcji przy użyciu ograniczonej ilości miejsca na stosie. Jest to świetne rozwiązanie do implementacji automatów stanowych, w tym rozszerzalnych automatów stanowych, i jest świetną kartą „wyjdź z więzienia” w wielu innych niezręcznych okolicznościach.

Na przykład sprawdź tę implementację problemu plecakowego 0-1, używając stylu kontynuacji przekazywania z zapamiętywaniem w języku F # od branży finansowej. Gdy masz wywołania ogona, styl przekazywania kontynuacji może być oczywistym rozwiązaniem, ale C ++ czyni go trudnym do rozwiązania.

Konkurencja

Innym oczywistym przykładem jest jednoczesne programowanie. Chociaż jest to całkowicie możliwe w C ++, jest bardzo podatne na błędy w porównaniu z innymi narzędziami, w szczególności komunikując procesy sekwencyjne, jak widać w językach takich jak Erlang, Scala i F #.

Jon Harrop
źródło
1

To stare pytanie, ale ponieważ nikt o nim nie wspominał, dodam listę (i teraz dyktuję) wyrażenia. Łatwo jest napisać linijkę w języku Haskell lub Python, która rozwiązuje problem Fizz-Buzz. Spróbuj to zrobić w C ++.

Podczas gdy C ++ dokonał ogromnych ruchów do nowoczesności za pomocą C ++ 11, trudno jest nazwać go „nowoczesnym” językiem. C ++ 17 (który nie został jeszcze wydany) robi jeszcze więcej ruchów, aby sprostać współczesnym standardom, o ile „nowoczesny” oznacza „nie z poprzedniego tysiąclecia”.

Nawet najprostsze ze słów, które można napisać w jednym wierszu w Pythonie (i przestrzeganie limitu długości linii znaków wynoszącego 79 znaków) stają się mnóstwem wierszy kodów po przetłumaczeniu na C ++, a niektóre z tych wierszy kodu C ++ są raczej skomplikowane.

David Hammen
źródło
Uwaga: większość mojego programowania jest w C ++. Lubię ten język.
David Hammen
Myślałem, że propozycja Ranges ma rozwiązać ten problem? (Myślę, że nawet w C ++ 17)
Martin Ba
2
„masowe przejście do nowoczesności”: Jakie „nowoczesne” funkcje zapewnia C ++ 11, które zostały wynalezione w obecnym tysiącleciu?
Giorgio
@MartinBa - Rozumiem propozycję „zakresów”, ponieważ zastępuje ona iteratory, które są łatwiejsze w obsłudze i mniej podatne na błędy. Nie widziałem żadnych sugestii, że pozwoliliby na coś tak interesującego jak listy.
Jules
2
@Giorgio - jakie cechy dowolnego obecnie popularnego języka zostały wynalezione w obecnym tysiącleciu?
Jules
0

Skompilowana biblioteka wywołująca wywołanie zwrotne, które jest zdefiniowaną przez użytkownika funkcją składową klasy zdefiniowanej przez użytkownika.


Jest to możliwe w Objective-C i sprawia, że ​​programowanie interfejsu użytkownika jest dziecinnie proste. Możesz powiedzieć przyciskowi: „Po naciśnięciu wywołaj tę metodę dla tego obiektu”, a przycisk zrobi to. Możesz dowolnie używać dowolnej nazwy metody dla wywołania zwrotnego, która ci się podoba, nie jest zawieszona w kodzie biblioteki, nie musisz dziedziczyć po adapterze, aby działała, ani kompilator nie chce rozwiązać wywołania w czasie kompilacji, i, co równie ważne, możesz powiedzieć dwóm przyciskom, aby wywoływały dwie różne metody tego samego obiektu.

Nie widziałem jeszcze tak elastycznego sposobu definiowania oddzwaniania w żadnym innym języku (choć bardzo chciałbym o nich usłyszeć!). Najbliższym odpowiednikiem w C ++ jest prawdopodobnie przekazanie funkcji lambda, która wykonuje wymagane wywołanie, co ponownie ogranicza kod biblioteki jako szablon.

To właśnie ta cecha Celu C nauczyła mnie doceniać zdolność języka do przekazywania dowolnego rodzaju obiektów / funkcji / cokolwiek-ważnego-pojęcia-języka-zawiera się swobodnie, wraz z mocą zapisywania ich do zmienne. Każdy punkt w języku, który definiuje dowolny rodzaj pojęcia, ale nie zapewnia sposobu przechowywania go (lub odniesienia do niego) we wszystkich dostępnych rodzajach zmiennych, jest znaczącą przeszkodą i prawdopodobnie źródłem wielu brzydkich, zduplikowany kod. Niestety, barokowe języki programowania mają zwykle wiele z następujących cech:

  • W C ++ nie można zapisać typu VLA ani przechowywać do niego wskaźnika. To skutecznie zabrania prawdziwych wielowymiarowych tablic o dynamicznym rozmiarze (które są dostępne w C od C99).

  • W C ++ nie można zapisać typu lambda. Nie możesz nawet tego wpisać. Tak więc nie ma sposobu na ominięcie lambdy lub zapisanie odniesienia do niej w obiekcie. Funkcje Lambda można przekazać tylko do szablonów.

  • W Fortran nie można zapisać typu listy nazw. Po prostu nie ma sposobu na przekazanie listy nazw do jakiejkolwiek procedury. Jeśli masz złożony algorytm, który powinien być w stanie obsłużyć dwie różne listy nazw, nie masz szczęścia. Nie można po prostu napisać algorytmu raz i przekazać mu odpowiednich list nazw.

To tylko kilka przykładów, ale widzisz wspólny punkt: ilekroć zobaczysz takie ograniczenie po raz pierwszy, zwykle nie będziesz się tym przejmować, ponieważ robienie zakazanej rzeczy wydaje się takie szalone. Jednak, gdy robisz poważne programowanie w tym języku, w końcu dochodzisz do punktu, w którym to dokładne ograniczenie staje się prawdziwą uciążliwością.

cmaster
źródło
1
I have not seen a similarly flexible way to define a callback in any other language yet (though I'd be very interested to hear about them!) To, co właśnie opisujesz, brzmi dokładnie tak, jak działa kod interfejsu użytkownika sterowany zdarzeniami w Delphi. (Oraz w .NET WinForms, na który Delphi miał duży wpływ.)
Mason Wheeler,
2
„W C ++ nie można zapisać typu VLA” [...] - w C ++ VLA w stylu C99 są niepotrzebne, ponieważ mamy std::vector. Chociaż jest nieco mniej wydajny ze względu na nieużywanie alokacji stosu, jest funkcjonalnie izomorficzny dla VLA, więc tak naprawdę nie liczy się jako problem typu „blub”: programiści C ++ mogą sprawdzić, jak to działa i po prostu powiedzieć „ah tak , C robi to wydajniej niż C ++ ”.
Jules
2
„W C ++ nie można zapisać typu lambda. Nie można nawet go wpisać. Dlatego nie ma możliwości ominięcia lambda ani przechowywania odniesienia do niej w obiekcie” - po to std::functionjest.
Jules
3
„Nie widziałem jeszcze tak elastycznego sposobu definiowania wywołania zwrotnego w żadnym innym języku (choć bardzo chciałbym o nich usłyszeć!).” - w Javie możesz pisać object::methodi zostanie on przekonwertowany na instancję dowolnego interfejsu, którego oczekuje kod odbiorczy. C # ma delegatów. Każdy obiektowo-funkcjonalny język ma tę funkcję, ponieważ jest to zasadniczo punkt przekroju dwóch paradygmatów.
Jules
@Jules Twoje argumenty są dokładnie tym, o co chodzi w Blub-Paradox: Jako biegły programista C ++ nie widzisz ich jako ograniczeń. Są to jednak ograniczenia, a inne języki, takie jak C99, mają większą moc w tych konkretnych punktach. Do ostatniego punktu: w wielu językach możliwe są obejścia, ale nie znam takiego, który naprawdę pozwala ci przekazać nazwę dowolnej metody do innej klasy i wywołać ją na obiekcie, który podasz.
cmaster