Il "paradosso blub" e c ++


37

Stavo leggendo l'articolo qui: http://www.paulgraham.com/avg.html e la parte relativa al "blub paradosso" è stata particolarmente interessante. Come qualcuno che principalmente codifica in c ++ ma è esposto ad altre lingue (principalmente Haskell) sono consapevole di alcune cose utili in queste lingue che sono difficili da replicare in c ++. La domanda è principalmente per le persone che sono competenti sia in c ++ che in qualche altra lingua, c'è qualche potente funzione linguistica o linguaggio che usi in un linguaggio che sarebbe difficile concettualizzare o implementare se scrivessi solo in c ++?

In particolare questa citazione ha attirato la mia attenzione:

Per induzione, gli unici programmatori in grado di vedere tutte le differenze di potere tra le varie lingue sono quelli che comprendono il più potente. (Questo è probabilmente ciò che Eric Raymond intendeva sul fatto che Lisp ti rendesse un programmatore migliore.) Non puoi fidarti delle opinioni degli altri, a causa del paradosso di Blub: sono soddisfatti di qualunque linguaggio si capisca, perché detta il come pensano ai programmi.

Se risulta che io sono l'equivalente del programmatore "Blub" in virtù dell'utilizzo di c ++, ciò solleva la seguente domanda: ci sono concetti o tecniche utili che hai incontrato in altre lingue che avresti trovato difficile concettualizzare se avessi stai scrivendo o "pensando" in c ++?

Ad esempio il paradigma di programmazione logica visto in linguaggi come Prolog e Mercury può essere implementato in c ++ usando la libreria di ricino ma alla fine trovo che concettualmente sto pensando in termini di codice Prolog e traducendo l'equivalente in c ++ quando lo uso. Come mezzo per ampliare le mie conoscenze di programmazione sto cercando di scoprire se ci sono altri esempi simili di modi di dire utili / potenti che sono espressi in modo più efficiente in altri linguaggi di cui potrei non essere a conoscenza come sviluppatore di c ++. Un altro esempio che viene in mente è il sistema macro in lisp, la generazione del codice del programma all'interno del programma sembra avere molti vantaggi per alcuni problemi. Questo sembra essere difficile da implementare e pensare all'interno di c ++.

Questa domanda non intende essere un dibattito "c ++ vs lisp" o qualsiasi tipo di dibattito di tipo guerre linguistiche. Fare una domanda come questa è l'unico modo in cui riesco a vedere possibile scoprire cose che non so di cui non so.



2
Sono d'accordo. Finché questo non si trasforma in un dibattito C ++ vs Lisp, penso che qui ci sia qualcosa da imparare.
jeffythedragonslayer

@MasonWheeler: there are things that other languages can do that Lisp can't- Improbabile, dal momento che Lisp è Turing completo. Forse intendevi dire che ci sono alcune cose che non sono pratiche da fare in Lisp? Potrei dire la stessa cosa di qualsiasi linguaggio di programmazione.
Robert Harvey,

2
@RobertHarvey: "Tutte le lingue sono ugualmente potenti nel senso di essere equivalenti a Turing, ma non è questo il senso della parola che i programmatori si preoccupano. (Nessuno vuole programmare una macchina di Turing.) formalmente definibile, ma un modo per spiegare sarebbe quello di dire che si riferisce a funzionalità che potresti ottenere solo nel linguaggio meno potente scrivendo un interprete per il linguaggio più potente in esso ". - Paul Graham, in una nota in calce al trollpost in questione. (Vedi cosa intendo?)
Mason Wheeler,

@Mason Wheeler: (Non proprio.)
Robert Harvey,

Risposte:


16

Bene, da quando hai menzionato Haskell:

  1. Pattern Matching. Trovo che la corrispondenza dei motivi sia molto più facile da leggere e scrivere. Considera la definizione di mappa e pensa a come verrebbe implementata in una lingua senza pattern matching.

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
  2. Il sistema di tipi. A volte può essere un dolore ma è estremamente utile. Devi programmare con esso per capirlo davvero e quanti bug cattura. Inoltre, la trasparenza referenziale è meravigliosa. Diventa evidente solo dopo aver programmato in Haskell per un po 'quanti bug sono causati dalla gestione dello stato in un linguaggio imperativo.

  3. Programmazione funzionale in generale. Utilizzo di mappe e pieghe anziché iterazione. Ricorsione. Si tratta di pensare a un livello superiore.

  4. Valutazione pigra. Ancora una volta si tratta di pensare a un livello superiore e lasciare che il sistema gestisca la valutazione.

  5. Cabala, pacchetti e moduli. Avere i pacchetti di download di Cabal per me è molto più conveniente che trovare il codice sorgente, scrivere un makefile, ecc. Essere in grado di importare solo determinati nomi è molto meglio che essenzialmente fare in modo che tutti i file sorgente vengano scaricati insieme e poi compilati.


2
Una nota sulla corrispondenza dei modelli: non direi che è più facile scrivere in generale, ma dopo aver letto un po 'il problema dell'espressione diventa chiaro che cose come if e switch statement, enums e il modello di osservatore sono tutte implementazioni inferiori di tipi di dati algebrici + Pattern Matching. (E non
permettiamoci

Quello che dici è vero, ma il problema dell'espressione riguarda le limitazioni dei tipi di dati algebrici (e le doppie limitazioni dello standard OOP).
Blaisorblade,

@hugomg Intendi lo schema Visitatore anziché Observer?
Sebastian Redl,

sì.
Cambio

@hugomg Non si tratta di avere Maybe(per C ++ vedi std::optional), si tratta di contrassegnare esplicitamente le cose come facoltative / nullable / forse.
Deduplicatore

7

Memoize!

Prova a scriverlo in C ++. Non con C ++ 0x.

Troppo ingombrante? Va bene, provalo con C ++ 0x.

Vedi se riesci a battere questo 4 righe (o 5-line, qualunque sia: P) in fase di compilazione versione 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));
}

Tutto quello che devi fare per chiamarlo è qualcosa del tipo:

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

Puoi anche provare qualcosa di simile in Scheme, anche se è un po 'più lento perché succede in fase di esecuzione e perché la ricerca qui è lineare invece di hash (e bene, perché è Scheme):

(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))

1
Quindi ami APL dove puoi scrivere qualsiasi cosa in una sola riga? Le dimensioni non contano!
Bo Persson,

@Bo: non ho usato APL. Non sono sicuro di cosa intendi per "dimensione non importa", ma c'è qualcosa di sbagliato nel mio codice che te lo fa dire? E c'è qualche vantaggio su come lo faresti in una lingua diversa (come C ++) di cui non sono a conoscenza? (Ho modificato un po 'i nomi delle variabili, nel caso fosse quello a cui ti riferivi.)
Mehrdad,

1
@Mehrdad - Il mio commento riguarda il programma più compatto non è un segno del miglior linguaggio di programmazione. In tal caso APL vincerebbe a mani basse, perché fai la maggior parte delle cose con un solo operatore di caratteri. L'unico problema è che è illeggibile.
Bo Persson,

@ Bo: Come ho detto, non stavo raccomandando APL; Non l'ho mai visto. La dimensione era un criterio (anche se significativo, come si può vedere se si prova questo con C ++) ... ma c'è qualcosa di sbagliato in questo codice?
Mehrdad,

1
@Matt: il tuo codice ha memorizzato una funzione, ma questo codice può memorizzare qualsiasi funzione. Questi non sono affatto equivalenti. Se in realtà provi a scrivere una funzione di ordine superiore come questa in C ++ 0x, è molto più noioso che in D (anche se è ancora del tutto possibile ... sebbene non sia possibile in C ++ 03).
Mehrdad,

5

Il C ++ è un linguaggio multiparadigm, il che significa che cerca di supportare molti modi di pensare. A volte una funzionalità C ++ è più scomoda o meno fluida dell'implementazione di un'altra lingua, come nel caso della programmazione funzionale.

Detto questo, non riesco a pensare alla parte superiore della mia testa di una funzionalità di linguaggio C ++ nativo che fa ciò che fa yieldin Python o JavaScript.

Un altro esempio è la programmazione concorrente . C ++ 0x ne avrà voce in capitolo, ma lo standard attuale no e la concorrenza è un modo di pensare completamente nuovo.

Inoltre, lo sviluppo rapido - anche la programmazione della shell - è qualcosa che non imparerai mai se non esci mai dal dominio della programmazione C ++.


Non riesco nemmeno a pensare a quanto sarebbe difficile creare generatori in C ++ dato C ++ 2003. Il C ++ 2011 lo renderebbe più semplice, ma comunque non banale. Lavorando di routine con C ++, C # e Python, i generatori sono facilmente la caratteristica che mi manca di più in C ++ (ora che C ++ 2011 ha aggiunto lambdas).

So che mi spareranno per questo, ma se dovessi assolutamente implementare generatori in C ++, dovrei usare ... setjmpe longjmp. Non ho idea di quanto si rompe, ma immagino che le eccezioni sarebbero le prime ad andare. Ora, se mi vuoi scusare, ho bisogno di rileggere Modern C ++ Design per togliermelo dalla testa.
Mike DeSimone,

@ Mike DeSimone, puoi approfondire (breifly) su come tenteresti una soluzione con setjmp e longjmp?

1
Le coroutine sono isomorfe ai funzionali.
GManNickG,

1
@Xeo, @Mike: xkcd.com/292
Mehrdad,

5

Le coroutine sono una funzione linguistica immensamente utile che sostiene molti dei vantaggi più tangibili di altre lingue rispetto al C ++. Fondamentalmente forniscono pile extra in modo che le funzioni possano essere interrotte e continuate, fornendo strutture simili a pipeline al linguaggio che alimentano facilmente i risultati delle operazioni attraverso i filtri ad altre operazioni. È meraviglioso, e in Ruby l'ho trovato molto intuitivo ed elegante. La valutazione pigra si lega anche a questo.

Compilazione / esecuzione / valutazione del codice di introspezione e run-time / qualunque cosa siano funzionalità estremamente potenti che mancano al C ++.


Le coroutine sono disponibili in FreeRTOS ( vedi qui ), che è implementato in C. Mi chiedo cosa ci vorrebbe per farle funzionare in C ++?
Mike DeSimone,

Le co-routine sono un brutto trucco per emulare oggetti in C. In C ++, gli oggetti sono usati per raggruppare codice e dati. Ma in C non puoi. Quindi si utilizza lo stack di routine per memorizzare i dati e la funzione di routine per contenere il codice.
MSalters,


1
@Ferruccio: grazie per il link ... ce ne sono anche alcuni nell'articolo di Wikipedia. @MSalters: cosa ti fa descrivere le co-routine come "un brutto trucco"? Mi sembra una prospettiva molto arbitraria. L'uso di uno stack per memorizzare lo stato è anche eseguito da algoritmi ricorsivi: sono anche hacker? FWIW, coroutine e OOP sono venuti in scena nello stesso periodo (primi anni '60) ... dire che il primo è un hack per oggetti in C sembra bizzarro ... Immagino che pochi programmatori C di allora fossero interessati a emulare oggetti, > 15 anni prima del C ++.
Tony

4

Avendo implementato un sistema di algebra computerizzata sia in Lisp che in C ++, posso dirti che il compito era molto più semplice in Lisp, anche se ero un principiante assoluto della lingua. Questa natura semplicistica di tutto ciò che viene elencato semplifica moltissimi algoritmi. Certo, la versione C ++ era miliardi di volte più veloce. Sì, avrei potuto rendere la versione di lisp più veloce, ma il codice non sarebbe stato altrettanto fluido. Lo scripting è un'altra cosa che sarà sempre più facile, ad esempio lisp. Si tratta solo di usare lo strumento giusto per il lavoro.


Qual è stata la differenza di velocità?
quant_dev,

1
@quant_dev: un multiplo di un milione, ovviamente!
Matt Ellen,

Non l'ho mai veramente misurato, ma ho avuto la sensazione che le O grandi fossero diverse. Inizialmente ho scritto la versione C ++ in uno stile funzionale e ha avuto problemi di velocità anche fino a quando non gli ho insegnato a modificare le strutture di dati invece di crearne di nuove e modificate. Ma ciò ha anche reso più difficile la lettura del codice ...
jeffythedragonslayer

2

Cosa intendiamo quando diciamo che una lingua è "più potente" di un'altra? Quando diciamo che una lingua è "espressiva?" O "ricco?" Penso che intendiamo dire che un linguaggio guadagna potere quando il suo campo visivo si restringe abbastanza da rendere facile e naturale descrivere un problema - davvero una transizione di stato, no? - che vive in quella prospettiva. Eppure quel linguaggio è considerevolmente meno potente, meno espressivo e meno utile quando il nostro campo visivo si allarga.

Più "potente" ed "espressivo" il linguaggio, più limitato è il suo utilizzo. Quindi forse "potente" ed "espressivo" sono le parole sbagliate da usare per uno strumento di ristretta utilità. Forse "appropriato" o "astratto" sono parole migliori per tali cose.

Ho iniziato a programmare scrivendo un sacco di cose di basso livello: i driver di dispositivo con le loro routine di interrupt; programmi integrati; codice del sistema operativo. Il codice era intimo con l'hardware e ho scritto tutto in linguaggio assembly. Non diremmo che l'assemblatore sia nel minimo astratto, eppure era ed è il linguaggio più potente ed espressivo di tutti. Posso esprimere qualsiasi problema nel linguaggio assembly; è così potente che posso fare qualsiasi cosa per favore con qualsiasi macchina.

E tutta la mia successiva comprensione del linguaggio di livello superiore deve tutto alla mia esperienza con assemblatore. Tutto quello che ho imparato in seguito è stato facile perché, vedi, tutto - non importa quanto astratto - alla fine deve adattarsi all'hardware.

Potresti voler dimenticare livelli sempre più alti di astrazione, ovvero campi visivi sempre più stretti. Puoi sempre prenderlo più tardi. È un gioco da ragazzi imparare, questione di giorni. Sarebbe meglio, secondo me, imparare la lingua dell'hardware 1 , avvicinarsi il più possibile all'osso.


1 Forse non abbastanza germano, ma care cdrprendono il loro nome dall'hardware: il primo Lisp funzionava su una macchina che aveva un vero Decrement Register e un vero Address Address Register. Che ne dici di quello?


Scoprite che la scelta è un'arma a doppio taglio. Lo cerchiamo tutti, ma c'è un lato oscuro e ci rende infelici. È meglio avere una visione ben definita del mondo e confini definiti in cui operare. Le persone sono creature molto creative e possono fare grandi cose con strumenti limitati. Quindi, per riassumere, sto dicendo che non è il linguaggio di programmazione, ma ho persone di talento che possono far cantare qualsiasi lingua!
Chad,

"Suggerire è creare; definire è distruggere". Pensare che potresti rendere la vita più facile con un'altra lingua ti fa sentire bene, ma una volta che fai il salto, allora devi affrontare le verruche della nuova lingua.
Mike DeSimone,

2
Direi che l'inglese è molto più potente ed espressivo di qualsiasi linguaggio di programmazione, eppure i limiti della sua utilità si espandono ogni giorno e la sua utilità è immensa. Parte del potere e dell'espressività deriva dalla capacità di comunicare al livello appropriato di astrazione e dalla capacità di inventare nuovi livelli di astrazione quando collegati.
molbdnilo,

@Mike allora devi occuparti di comunicare con la lingua precedente all'interno della nuova;)
Ciad

2

Matrici associative

Un modo tipico di elaborare i dati è:

  • leggendo l'input e costruendone una struttura gerarchica,
  • creazione di indici per quella struttura (ad es. ordine diverso),
  • creando estratti (parti filtrate) di essi,
  • trovare un valore o un gruppo di valore (nodo),
  • riorganizzare la struttura (eliminare nodi, aggiungere, aggiungere, rimuovere elementi secondari in base a una regola, ecc.),
  • scansiona l'albero e stampalo o salvane alcune parti.

Lo strumento giusto è la matrice associativa .

  • Il miglior supporto linguistico per gli array associativi che ho visto è MUMPS , in cui gli array associativi sono: 1. sempre ordinati 2. possono essere creati su disco (il cosiddetto database), con la stessa sintassi. (Effetto collaterale: è estremamente potente come database, il programmatore ha accesso al btree nativo. Il miglior sistema NoSQL di sempre.)
  • Il mio secondo premio va a PHP , mi piace foreach e una sintassi semplice, come $ a [] = x o $ a [x] [y] [z] ++ .

Non mi piace molto la sintassi dell'array associativo di JavaScript, perché non riesco a creare, ad esempio [x] [y] [z] = 8 , prima devo creare un [x] e un [x] [y] .

Ok, in C ++ (e in Java) ci sono un bel portfolio di classi container, Map , Multimap , qualunque cosa, ma se voglio scansionare, devo fare un iteratore, e quando voglio inserire un nuovo elemento di livello profondo, io devono creare tutti i livelli superiori ecc. A disagio.

Non dico che non ci sono array associativi utilizzabili in C ++ (e Java), ma i linguaggi di script tipografici (o tipizzati non rigorosi) battono quelli compilati, perché sono linguaggi di script tipografici.

Disclaimer: non ho familiarità con C # e altre lingue .NET, AFAIK hanno una buona gestione associativa dell'array.


1
Divulgazione completa: MUMPS non è per tutti. Citazione: per dare un esempio un po 'più "reale" dell'orrore che è MUMPS, inizia prendendo una parte del Concorso Internazionale Codice C Offuscato, un trattino di Perl, due misure di raccolta di FORTRAN e SNOBOL e i contributi indipendenti e non coordinati di dozzine di ricercatori medici, e il gioco è fatto.
Mike DeSimone,

1
In Python, è possibile utilizzare il tipo incorporato dict(ad esempio x = {0: 5, 1: "foo", None: 500e3}, si noti che non è necessario che chiavi o valori siano dello stesso tipo). Cercare di fare qualcosa di simile a[x][y][z] = 8è difficile perché la lingua deve guardare al futuro per vedere se hai intenzione di impostare un valore o creare un altro livello; l'espressione a[x][y]da sola non te lo dice.
Mike DeSimone,

MUMPS è originariamente un linguaggio di tipo Basic con array associativi (può essere archiviato direttamente sul disco!). Le versioni successive contengono estensioni procedurali, il che lo rende molto simile al core PHP. Una paura di Basic e PHP dovrebbe trovare la parotite spaventosa, ma altri no. I programmatori no. E ricorda, è un sistema molto vecchio, tutte cose strane, come istruzioni di una lettera (anche se puoi usare nomi completi), ordine di valutazione LR ecc. - così come soluzioni non strane - hanno un solo obiettivo: l' ottimizzazione .
ern0

"Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali. Eppure non dovremmo rinunciare alle nostre opportunità in quel 3% critico". - Donald Knuth. Quello che descrivi mi sembra un linguaggio legacy la cui enfasi è la compatibilità con le versioni precedenti, e va bene. Personalmente, in quelle applicazioni, considero la manutenibilità più importante dell'ottimizzazione e un linguaggio con espressioni non algebriche e comandi a una lettera sembra controproducente. Servo il cliente, non la lingua.
Mike DeSimone,

@Mike: link molto divertenti che hai pubblicato, mi sono fatto una bella risata mentre li leggevo.
shuttle87,

2

Non ho imparato Java, C \ C ++, Assembly e Java Script. Uso C ++ per guadagnarmi da vivere.

Tuttavia, dovrei dire che mi piace di più la programmazione dell'Assemblea e la programmazione in C. Ciò è in linea principalmente con la programmazione imperativa.

So che i paradigmi di programmazione sono importanti per classificare i tipi di dati e fornire concetti astratti di programmazione più elevata per consentire potenti schemi di progettazione e formalizzazione del codice. Anche se in un certo senso, ogni Paradigms è una raccolta di modelli e raccolte per astrarre il livello hardware sottostante, quindi non è necessario pensare a EAX o IP internamente alla macchina.

Il mio unico problema con questo, è che le nozioni dei popoli e i concetti di come la macchina lavora si trasformano nelle affermazioni ambigue dell'ideologia e di ciò che sta succedendo. Questo pane è di tutti i tipi di meravigliose astrazioni in cima agli abstract di un obiettivo ideologico del programmatore.

Alla fine della giornata, è meglio avere una buona mentalità e confini chiari su ciò che è la CPU e su come i computer lavorano sotto il cofano. Tutto ciò che interessa alla CPU è eseguire una serie di istruzioni che spostano i dati dentro e fuori la memoria in un registro ed eseguono un'istruzione. Non ha alcun concetto di tipo di dati o concetti di programmazione più elevati. Sposta solo i dati.

Diventa più complesso quando aggiungi paradigmi di programmazione nel mix perché la nostra visione del mondo è diversa.


2

c'è qualche potente linguaggio o linguaggio che usi in un linguaggio che sarebbe difficile concettualizzare o implementare se scrivessi solo in c ++?

Ci sono concetti o tecniche utili che hai incontrato in altre lingue che avresti trovato difficile concettualizzare se avessi scritto o "pensato" in c ++?

Il C ++ rende molti approcci non trattabili. Direi che la maggior parte della programmazione è difficile da concettualizzare se ti limiti al C ++. Ecco alcuni esempi di problemi che sono risolti molto più facilmente nei modi in cui C ++ rende difficile.

Assegnazione dei registri e convenzioni di chiamata

Molte persone pensano al C ++ come a un linguaggio di basso livello bare metal ma non lo è. Sottraendo i dettagli importanti della macchina, C ++ rende difficile concettualizzare funzionalità come l'assegnazione dei registri e le convenzioni di chiamata.

Per conoscere concetti come questi, ti consiglio di provare la programmazione del linguaggio assembly e leggere questo articolo sulla qualità della generazione del codice ARM .

Generazione di codice runtime

Se conosci solo C ++, probabilmente pensi che i modelli siano il tutto e la fine della metaprogrammazione. Non lo sono. In realtà, sono uno strumento oggettivamente negativo per la metaprogrammazione. Ogni programma che manipola un altro programma è un metaprogramma, inclusi interpreti, compilatori, sistemi di algebra del computer e dimostratori di teoremi. La generazione del codice di runtime è una funzione utile per questo.

Raccomando di avviare un'implementazione dello Schema e di giocare EVALper conoscere la valutazione metacircolare.

Manipolazione degli alberi

Gli alberi sono ovunque in programmazione. Nell'analisi hai alberi di sintassi astratti. Nei compilatori hai IR che sono alberi. Nella programmazione grafica e GUI hai alberi di scene.

Questo "Parser JSON ridicolmente semplice per C ++" pesa solo 484 LOC, che è molto piccolo per C ++. Ora confrontalo con il mio semplice parser JSON che pesa solo a 60 LOC di F #. La differenza è principalmente perché i tipi di dati algebrici di ML e la corrispondenza dei modelli (compresi i modelli attivi) rendono molto più semplice la manipolazione degli alberi.

Scopri anche alberi rosso-neri in OCaml .

Strutture dati puramente funzionali

La mancanza di GC in C ++ rende praticamente impossibile adottare alcuni approcci utili. Le strutture di dati puramente funzionali sono uno di questi strumenti.

Ad esempio, dai un'occhiata a questo abbinatore di espressioni regolari a 47 righe in OCaml. La brevità è dovuta in gran parte all'uso estensivo di strutture di dati puramente funzionali. In particolare, l'uso di dizionari con chiavi che sono insiemi. Questo è davvero difficile da fare in C ++ perché i dizionari e gli insiemi stdlib sono tutti mutabili ma non è possibile mutare le chiavi di un dizionario o interrompere la raccolta.

La programmazione logica e i buffer di annullamento sono altri esempi pratici in cui le strutture di dati puramente funzionali rendono qualcosa di difficile in C ++ molto semplice in altri linguaggi.

Chiamate di coda

Non solo il C ++ non garantisce le chiamate di coda, ma RAII è fondamentalmente in contrasto con esso perché i distruttori si frappongono a una chiamata in posizione di coda. Le chiamate di coda consentono di effettuare un numero illimitato di chiamate di funzione utilizzando solo una quantità limitata di spazio stack. Questo è ottimo per l'implementazione di macchine a stati, comprese macchine a stati estensibili ed è un'ottima carta "esci di prigione" in molte circostanze altrimenti imbarazzanti.

Ad esempio, dai un'occhiata a questa implementazione del problema dello zaino 0-1 usando lo stile di passaggio di continuazione con memoization in F # dal settore finanziario. Quando si hanno chiamate di coda, lo stile di passaggio di continuazione può essere una soluzione ovvia, ma C ++ lo rende intrattabile.

Concorrenza

Un altro esempio ovvio è la programmazione concorrente. Sebbene ciò sia del tutto possibile in C ++, è estremamente soggetto a errori rispetto ad altri strumenti, in particolare per comunicare processi sequenziali come visto in lingue come Erlang, Scala e F #.


1

Questa è una vecchia domanda, ma dal momento che nessuno l'ha menzionata, aggiungerò la lista (e ora detta) le comprensioni. È facile scrivere una riga in Haskell o Python che risolva il problema Fizz-Buzz. Prova a farlo in C ++.

Mentre C ++ ha fatto enormi passi avanti nella modernità con C ++ 11, è un po 'difficile definirlo un linguaggio "moderno". Il C ++ 17 (che non è stato ancora rilasciato) sta facendo ancora più mosse per arrivare agli standard moderni, purché "moderno" significhi "non dal millennio precedente".

Anche la più semplice comprensione che uno può scrivere in una riga in Python (e obbedendo al limite di lunghezza di 79 caratteri di Guido) diventa un sacco e molte righe di codice quando tradotto in C ++, e alcune di quelle righe di codice C ++ sono piuttosto contorte.


Nota bene: la maggior parte della mia programmazione è in C ++. Mi piace la lingua.
David Hammen,

Pensavo che la proposta Ranges avrebbe dovuto risolvere questo? (Neanche in C ++ 17 credo)
Martin Ba,

2
"enormi passi verso la modernità": quali caratteristiche "moderne" fornisce C ++ 11 che sono state inventate nell'attuale millennio?
Giorgio,

@MartinBa - La mia comprensione della proposta "range" è che si tratta di un sostituto per iteratori che sono più facili da lavorare e meno soggetti a errori. Non ho visto alcun suggerimento sul fatto che avrebbero permesso qualcosa di così interessante come la comprensione dell'elenco.
Jules,

2
@Giorgio - quali caratteristiche di qualsiasi lingua attualmente popolare sono state inventate nell'attuale millennio?
Jules,

0

Una libreria compilata che chiama un callback, che è una funzione membro definita dall'utente di una classe definita dall'utente.


Ciò è possibile in Objective-C e semplifica la programmazione dell'interfaccia utente. Puoi dire a un pulsante: "Per favore, chiama questo metodo per questo oggetto quando viene premuto", e il pulsante lo farà. Sei libero di utilizzare qualsiasi nome di metodo per il callback che ti piace, non è bloccato nel codice della libreria, non devi ereditare da un adattatore per farlo funzionare, né il compilatore vuole risolvere la chiamata al momento della compilazione, e, altrettanto importante, puoi dire a due pulsanti di chiamare due diversi metodi dello stesso oggetto.

Non ho ancora visto un modo altrettanto flessibile per definire un callback in qualsiasi altra lingua (anche se sarei molto interessato a sentir parlare di loro!). L'equivalente più vicino in C ++ sta probabilmente passando una funzione lambda che esegue la chiamata richiesta, che limita nuovamente il codice della libreria a essere un modello.

È questa caratteristica di Objective-C che mi ha insegnato a valutare la capacità di un linguaggio di trasmettere liberamente qualsiasi tipo di oggetti / funzioni / qualunque cosa il concetto importante contenga il linguaggio, insieme al potere di salvarli in variabili. Qualsiasi punto in un linguaggio che definisce qualsiasi tipo di concetto, ma non fornisce un mezzo per memorizzarlo (o un riferimento ad esso) in tutti i tipi di variabili disponibili, è un ostacolo significativo e probabilmente una fonte di molto brutto, codice duplicato. Sfortunatamente, i linguaggi di programmazione barocca tendono ad esibire una serie di questi punti:

  • In C ++ non è possibile scrivere il tipo di un VLA, né memorizzare un puntatore ad esso. Ciò proibisce effettivamente vere matrici multidimensionali di dimensioni dinamiche (che sono disponibili in C dal C99).

  • In C ++ non è possibile scrivere il tipo di lambda. Non puoi nemmeno scriverlo. Pertanto, non è possibile passare intorno a una lambda o memorizzare un riferimento ad essa in un oggetto. Le funzioni Lambda possono essere passate solo ai modelli.

  • In Fortran non puoi scrivere il tipo di un elenco nomi. Semplicemente non c'è modo di passare una lista dei nomi a qualsiasi tipo di routine. Quindi, se hai un algoritmo complesso che dovrebbe essere in grado di gestire due diverse liste di nomi, sei sfortunato. Non puoi semplicemente scrivere l'algoritmo una volta e passargli le relative liste dei nomi.

Questi sono solo alcuni esempi, ma vedi il punto comune: ogni volta che vedi una tale restrizione per la prima volta, di solito non ti importerai perché sembra un'idea così folle fare la cosa proibita. Tuttavia, quando esegui una seria programmazione in quella lingua, alla fine arrivi al punto in cui questa restrizione precisa diventa un vero fastidio.


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!) Quello che hai appena descritto suona esattamente come il modo in cui il codice dell'interfaccia utente basata sugli eventi funziona in Delphi. (E in .NET WinForms, fortemente influenzato da Delphi.)
Mason Wheeler,

2
"In C ++ non è possibile scrivere il tipo di un VLA" [...] - in C ++, i VLA in stile C99 non sono necessari, perché abbiamo std::vector. Sebbene sia un po 'meno efficiente a causa del non utilizzo dell'allocazione dello stack, è funzionalmente isomorfo a un VLA, quindi non conta davvero come un problema di tipo "blub": i programmatori C ++ possono vedere come funziona e dire semplicemente "ah sì , C lo fa in modo più efficiente di C ++ ".
Jules,

2
"In C ++ non puoi scrivere il tipo di lambda. Non puoi nemmeno scriverlo. Quindi, non c'è modo di passare un lambda o di memorizzarne un riferimento in un oggetto" - questo è ciò che std::functionserve.
Jules,

3
"Non ho ancora visto un modo altrettanto flessibile per definire un callback in nessun'altra lingua (anche se sarei molto interessato a sentirne parlare!)." - in Java, puoi scrivere object::methode verrà convertito in un'istanza di qualunque interfaccia si aspetti il ​​codice di ricezione. C # ha delegati. Ogni linguaggio funzionale all'oggetto ha questa caratteristica perché è fondamentalmente il punto di sezione trasversale dei due paradigmi.
Jules,

@Jules I tuoi argomenti sono esattamente ciò di cui parla Blub-Paradox: come abile programmatore C ++, non li vedi come limiti. Tuttavia, sono limitazioni e altre lingue come C99 sono più potenti in questi punti specifici. All'ultimo punto: ci sono soluzioni alternative possibili in molte lingue, ma non ne conosco uno che ti permetta davvero di passare il nome di un metodo a un'altra classe e farlo chiamare su un oggetto che fornisci pure.
cmaster
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.