La "paradoja del blub" y c ++

37

Estaba leyendo el artículo aquí: http://www.paulgraham.com/avg.html y la parte sobre la "paradoja del blub" fue particularmente interesante. Como alguien que codifica principalmente en c ++ pero tiene exposición a otros idiomas (principalmente Haskell), conozco algunas cosas útiles en estos idiomas que son difíciles de replicar en c ++. La pregunta es principalmente para las personas que son competentes tanto en c ++ como en algún otro idioma, ¿hay alguna característica o idioma de lenguaje poderoso que utilice en un lenguaje que sería difícil de conceptualizar o implementar si solo estuviera escribiendo en c ++?

En particular, esta cita me llamó la atención:

Por inducción, los únicos programadores en condiciones de ver todas las diferencias de poder entre los distintos idiomas son aquellos que entienden el más poderoso. (Esto es probablemente lo que Eric Raymond quiso decir con Lisp para hacerte un mejor programador). No puedes confiar en las opiniones de los demás, debido a la paradoja de Blub: están satisfechos con el lenguaje que usan, porque dicta el cómo piensan sobre los programas.

Si resulta que soy el equivalente del programador "Blub" en virtud del uso de c ++, esto plantea la siguiente pregunta: ¿Hay algún concepto o técnica útil que haya encontrado en otros idiomas que le hubiera resultado difícil de conceptualizar si hubiera estado escribiendo o "pensando" en c ++?

Por ejemplo, el paradigma de programación lógica que se ve en lenguajes como Prolog y Mercury se puede implementar en c ++ usando la biblioteca castor, pero finalmente encuentro que conceptualmente estoy pensando en términos de código Prolog y traduciendo al equivalente de c ++ al usar esto. Como una forma de ampliar mi conocimiento de programación, estoy tratando de descubrir si hay otros ejemplos similares de expresiones útiles / poderosas que se expresen de manera más eficiente en otros lenguajes que no conozco como desarrollador de c ++. Otro ejemplo que viene a la mente es el sistema macro en lisp, generar el código del programa desde dentro del programa parece tener muchos beneficios para algunos problemas. Esto parece ser difícil de implementar y pensar desde dentro de c ++.

Esta pregunta no pretende ser un debate "c ++ vs lisp" ni ningún tipo de debate de tipo guerras lingüísticas. Hacer una pregunta como esta es la única forma en que puedo ver posible descubrir cosas que no sé sobre las que no sé.

lanzadera87
fuente
2
Estoy de acuerdo. Mientras esto no se convierta en un debate C ++ vs Lisp, creo que hay algo que aprender aquí.
jeffythedragonslayer
@MasonWheeler: there are things that other languages can do that Lisp can't- Es poco probable, ya que Lisp está completo en Turing. ¿Quizás quisiste decir que hay algunas cosas que no son prácticas para hacer en Lisp? Podría decir lo mismo sobre cualquier lenguaje de programación.
Robert Harvey
2
@RobertHarvey: "Todos los idiomas son igualmente poderosos en el sentido de ser equivalentes a Turing, pero ese no es el sentido de la palabra que les importa a los programadores. (Nadie quiere programar una máquina de Turing). Puede que no sea el tipo de poder que les importa a los programadores. formalmente definible, pero una forma de explicarlo sería decir que se refiere a características que solo se pueden obtener en el lenguaje menos poderoso escribiendo un intérprete para el lenguaje más poderoso ". - Paul Graham, en una nota al pie del trollpost en cuestión. (¿Ves lo que quiero decir?)
Mason Wheeler
@Mason Wheeler: (No realmente)
Robert Harvey

Respuestas:

16

Bueno, desde que mencionaste a Haskell:

  1. La coincidencia de patrones. Creo que la coincidencia de patrones es mucho más fácil de leer y escribir. Considere la definición de mapa y piense cómo se implementaría en un lenguaje sin coincidencia de patrones.

    map :: (a -> b) -> [a] -> [b]
    map f [] = []
    map f (x:xs) = f x : map f xs
  2. El sistema de tipos. A veces puede ser un dolor, pero es extremadamente útil. Tienes que programar con él para comprenderlo realmente y cuántos errores atrapa. Además, la transparencia referencial es maravillosa. Solo se hace evidente después de programar en Haskell durante un tiempo cuántos errores son causados ​​por la administración del estado en un lenguaje imperativo.

  3. Programación funcional en general. Usando mapas y pliegues en lugar de iteración. Recursividad Se trata de pensar en un nivel superior.

  4. Evaluación perezosa. Nuevamente, se trata de pensar en un nivel superior y dejar que el sistema maneje la evaluación.

  5. Cabal, paquetes y módulos. Para mí, tener paquetes de descarga de Cabal es mucho más conveniente que encontrar el código fuente, escribir un archivo MAKE, etc. Ser capaz de importar solo ciertos nombres es mucho mejor que esencialmente tener todos los archivos fuente volcados juntos y luego compilados.

Joel Burget
fuente
2
Una nota sobre la coincidencia de patrones: no diría que es más fácil de escribir en general, pero después de leer un poco sobre el problema de expresión, queda claro que cosas como if y switch declaraciones, enumeraciones y el patrón de observador son implementaciones inferiores de tipos de datos algebraicos + Coincidencia de patrones. (Y ni siquiera
comencemos
Lo que usted dice es cierto, pero el problema de la expresión se trata de las limitaciones de los tipos de datos algebraicos (y de las limitaciones duales de la OOP estándar).
Blaisorblade
@hugomg ¿Querías decir Patrón de visitante en lugar de Observador?
Sebastian Redl
sí. Siempre cambio esos dos nombres :)
hugomg
@hugomg No se trata de tener Maybe(para C ++ ver std::optional), se trata de tener que marcar explícitamente las cosas como opcional / anulable / tal vez.
Deduplicador
7

Memoize!

Intenta escribirlo en C ++. No con C ++ 0x.

Demasiado engorroso? Bien, pruébalo con C ++ 0x.

Vea si puede superar esta versión en tiempo de compilación de 4 líneas (o 5 líneas, lo que sea: P) en 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));
}

Todo lo que necesita hacer para llamarlo es algo como:

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

También puede probar algo similar en Scheme, aunque es un poco más lento porque ocurre en tiempo de ejecución y porque la búsqueda aquí es lineal en lugar de hash (y bueno, porque es 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))
Mehrdad
fuente
1
¿Entonces te encanta APL donde puedes escribir cualquier cosa en una sola línea? ¡El tamaño no importa!
Bo Persson
@Bo: No he usado APL. No estoy seguro de lo que quieres decir con "el tamaño no importa", pero ¿hay algún problema con mi código que te haga decir eso? ¿Y hay alguna ventaja en cómo haría esto en un lenguaje diferente (como C ++) que no conozco? (Edité un poco los nombres de las variables, en caso de que a eso se refiriera).
Mehrdad,
1
@Mehrdad: mi comentario es que el programa más compacto no es una señal del mejor lenguaje de programación. En ese caso, APL ganaría sin dudas, porque usted hace la mayoría de las cosas con un solo operador de caracteres. El único problema es que es ilegible.
Bo Persson
@Bo: Como dije, no estaba recomendando APL; Nunca lo he visto. El tamaño era un criterio (aunque significativo, como se puede ver si intentas esto con C ++) ... pero ¿hay algún problema con este código?
Mehrdad
1
@Matt: su código memorizó una función, pero este código puede memorizar cualquier función. Estos no son realmente equivalentes en absoluto. Si realmente intenta escribir una función de orden superior como esta en C ++ 0x, es mucho más tedioso que en D (aunque todavía es bastante posible ... aunque no es posible en C ++ 03).
Mehrdad
5

C ++ es un lenguaje multiparadigma, lo que significa que trata de admitir muchas formas de pensar. A veces, una característica de C ++ es más incómoda o menos fluida que la implementación de otro lenguaje, como es el caso de la programación funcional.

Dicho esto, no puedo pensar en la cabeza de una función nativa del lenguaje C ++ que hace lo que hace yieldPython o JavaScript.

Otro ejemplo es la programación concurrente . C ++ 0x tendrá algo que decir al respecto, pero el estándar actual no, y la concurrencia es una forma completamente nueva de pensar.

Además, el desarrollo rápido, incluso la programación de shell, es algo que nunca aprenderá si nunca abandona el dominio de la programación C ++.

Wilhelmtell
fuente
Ni siquiera puedo comenzar a pensar lo difícil que sería crear generadores en C ++ dado C ++ 2003. C ++ 2011 lo haría más fácil, pero aún no es trivial. Trabajando rutinariamente con C ++, C # y Python, los generadores son fácilmente la característica que más extraño en C ++ (ahora que C ++ 2011 ha agregado lambdas).
Sé que me dispararán por esto, pero si tuviera que implementar generadores en C ++, tendría que usar ...setjmp y longjmp. No tengo idea de cuánto se rompe, pero supongo que las excepciones serían las primeras en desaparecer. Ahora, si me disculpa, necesito volver a leer Modern C ++ Design para sacar eso de mi cabeza.
Mike DeSimone
@ Mike DeSimone, ¿puedes explicar (brevemente) cómo intentarías una solución con setjmp y longjmp?
1
Las corutinas son isomorfas a los functores.
GManNickG
1
@Xeo, @Mike: xkcd.com/292
Mehrdad
5

Las rutinas son una característica de lenguaje inmensamente útil que apuntalan muchos de los beneficios más tangibles de otros lenguajes sobre C ++. Básicamente proporcionan pilas adicionales para que las funciones se puedan interrumpir y continuar, proporcionando facilidades similares a las de una tubería al lenguaje que alimenta fácilmente los resultados de las operaciones a través de filtros a otras operaciones. Es maravilloso, y en Ruby lo encontré muy intuitivo y elegante. La evaluación perezosa también se relaciona con esto.

La introspección y la compilación / ejecución / evaluación de código en tiempo de ejecución / lo que sea son características enormemente poderosas de las que carece C ++.

Tony
fuente
Las rutinas están disponibles en FreeRTOS ( ver aquí ), que se implementa en C. Me pregunto qué se necesitaría para que funcionen en C ++.
Mike DeSimone
Las co-rutinas son un truco desagradable para emular objetos en C. En C ++, los objetos se usan para agrupar código y datos. Pero en C, no puedes. Entonces usa la pila de co-rutina para almacenar los datos, y la función de co-rutina para guardar el código.
MSalters
Coroutines en C ++: crystalclearsoftware.com/soc/coroutine
Ferruccio
1
@Ferruccio: Gracias por el enlace ... también hay algunos en el artículo de Wikipedia. @MSalters: ¿qué te hace describir las co-rutinas como "un truco desagradable"? Me parece una perspectiva muy arbitraria. El uso de una pila para almacenar el estado también se realiza mediante algoritmos recursivos: ¿también son hackeos? FWIW, coroutines y OOP aparecieron en la escena aproximadamente al mismo tiempo (principios de la década de 1960) ... decir que el primero es un truco para objetos en C parece extraño ... Me imagino que pocos programadores de C en ese entonces estaban interesados ​​en emular objetos, > 15 años antes de C ++.
Tony
4

Habiendo implementado un sistema de álgebra computacional en Lisp y C ++, puedo decirte que la tarea fue mucho más fácil en Lisp, a pesar de que era un novato en el lenguaje. Esta naturaleza simplista de todo lo que se enumera simplifica una gran cantidad de algoritmos. Por supuesto, la versión C ++ fue muchísimo más rápida. Sí, podría haber hecho la versión lisp más rápida, pero el código no sería tan lispy. La creación de secuencias de comandos es otra cosa que siempre será más fácil, por ejemplo, el lisp. Se trata de utilizar la herramienta adecuada para el trabajo.

jeffythedragonslayer
fuente
¿Cuál fue la diferencia en velocidad?
quant_dev
1
@quant_dev: ¡un múltiplo de un trillón, por supuesto!
Matt Ellen
Realmente nunca lo medí, pero tuve la sensación de que las grandes O eran diferentes. Originalmente escribí la versión de C ++ en un estilo funcional y también tenía problemas de velocidad hasta que le enseñé a modificar las estructuras de datos en lugar de crear nuevas y modificadas. Pero eso también hizo que el código fuera más difícil de leer ...
jeffythedragonslayer
2

¿Qué queremos decir cuando decimos que un idioma es "más poderoso" que otro? Cuando decimos que un lenguaje es "expresivo"? O "rico"? Creo que queremos decir que un lenguaje gana poder cuando su campo de visión se reduce lo suficiente como para que sea fácil y natural describir un problema, realmente una transición de estado, ¿no? - Que vive dentro de esa vista. Sin embargo, ese lenguaje es considerablemente menos poderoso, menos expresivo y menos útil cuando nuestro campo de visión se amplía.

Cuanto más "poderoso" y "expresivo" es el lenguaje, más limitado es su uso. Entonces, tal vez "poderoso" y "expresivo" son las palabras incorrectas para una herramienta de utilidad limitada. Tal vez "apropiado" o "abstracto" son mejores palabras para tales cosas.

Comencé en programación escribiendo un montón de cosas de bajo nivel: controladores de dispositivos con sus rutinas de interrupción; programas incrustados; código del sistema operativo El código era íntimo con el hardware y lo escribí todo en lenguaje ensamblador. No diríamos que ensamblador es en lo más abstracto, pero fue y es el lenguaje más poderoso y expresivo de todos. Puedo expresar cualquier problema en lenguaje ensamblador; es tan poderoso que puedo hacer lo que quiera con cualquier máquina.

Y toda mi comprensión posterior del lenguaje de nivel superior le debe todo a mi experiencia con el ensamblador. Todo lo que aprendí más tarde fue fácil porque, como ven, todo, no importa cuán abstracto, al final debe adaptarse al hardware.

Es posible que desee olvidarse de niveles de abstracción cada vez más altos, es decir, campos de visión cada vez más estrechos. Siempre puedes recoger eso más tarde. Es muy fácil de aprender, es cuestión de días. Sería mejor, en mi opinión, aprender el lenguaje del hardware 1 , acercarse lo más posible al hueso.


1 Quizás no del todo pertinente, pero cary cdrtome sus nombres del hardware: el primer Lisp se ejecutó en una máquina que tenía un Registro de decremento real y un Registro de dirección real. ¿Qué tal eso?

Pete Wilson
fuente
Encuentras que la elección es una espada de doble filo. Todos lo buscamos, pero hay un lado oscuro y nos hace infelices. Es mejor tener una visión muy definida del mundo y límites definidos en los que pueda operar. Las personas son criaturas muy creativas y pueden hacer grandes cosas con herramientas limitadas. Para resumir, ¡digo que no es el lenguaje de programación sino tener personas con talento que puedan hacer que cualquier lenguaje cante!
Chad
"Sugerir es crear; definir es destruir". Pensar que podrías hacer la vida más fácil con otro idioma se siente bien, pero una vez que das el salto, tienes que lidiar con las verrugas del nuevo idioma.
Mike DeSimone
2
Diría que el inglés es mucho más poderoso y expresivo que cualquier lenguaje de programación, y sin embargo, los límites de su utilidad se expanden cada día y su utilidad es inmensa. Parte del poder y la expresividad provienen de la capacidad de comunicarse al nivel apropiado de abstracción y la capacidad de inventar nuevos niveles de abstracción cuando están integrados.
molbdnilo
@Mike, entonces tienes que lidiar con la comunicación con el idioma anterior dentro del nuevo;)
Chad
2

Matrices asociativas

Una forma típica de procesar datos es:

  • leyendo la entrada y construyendo una estructura jerárquica a partir de ella,
  • crear índices para esa estructura (por ejemplo, diferente orden),
  • creando extractos (partes filtradas) de ellos,
  • encontrar un valor o un grupo de valores (nodo),
  • reorganizar la estructura (eliminar nodos, agregar, agregar, eliminar subelementos basados ​​en una regla, etc.),
  • escanee a través del árbol e imprima o guarde algunas partes de ellos.

La herramienta adecuada para ello es la matriz asociativa .

  • El mejor soporte de idioma para las matrices asociativas que he visto es MUMPS , donde las matrices asociativas son: 1. siempre ordenadas 2. se pueden crear en el disco (la llamada base de datos), con la misma sintaxis. (Efecto secundario: es extremadamente potente como base de datos, el programador tiene acceso al btree nativo. El mejor sistema NoSQL de la historia).
  • Mi segundo premio es para PHP , me gusta foreach y sintaxis fácil, como $ a [] = x o $ a [x] [y] [z] ++ .

Realmente no me gusta la sintaxis de matriz asociativa de JavaScript, porque no puedo crear, digamos un [x] [y] [z] = 8 , primero tengo que crear un [x] y un [x] [y] .

De acuerdo, en C ++ (y en Java) hay una buena cartera de clases de contenedor, Map , Multimap , en absoluto, pero si quiero escanear, tengo que hacer un iterador, y cuando quiero insertar un nuevo elemento de nivel profundo, yo Hay que crear todos los niveles superiores, etc. Incómodo.

No digo que no haya matrices asociativas utilizables en C ++ (y Java), pero los lenguajes de script sin tipo (o de tipo no estricto) superan a los compilados, porque son lenguajes de script sin tipo.

Descargo de responsabilidad: no estoy familiarizado con C # y otros idiomas .NET, AFAIK tienen un buen manejo de matriz asociativa.

ern0
fuente
1
Divulgación completa: MUMPS no es para todos. Cita: Para dar un poco más de ejemplo del "mundo real" del horror que es MUMPS, comience tomando una parte del Concurso Internacional de Código C Ofuscado, una pizca de Perl, dos medidas colmadas de FORTRAN y SNOBOL, y las contribuciones independientes y descoordinadas de docenas de investigadores médicos, y ahí lo tienes.
Mike DeSimone
1
En Python, puede usar el tipo incorporado dict(por ejemplo x = {0: 5, 1: "foo", None: 500e3}, tenga en cuenta que no hay requisitos para que las claves o los valores sean del mismo tipo). Intentar hacer algo así a[x][y][z] = 8es difícil porque el lenguaje tiene que mirar hacia el futuro para ver si vas a establecer un valor o crear otro nivel; la expresión a[x][y]en sí misma no te dice.
Mike DeSimone
MUMPS es originalmente un lenguaje de tipo básico con matrices asociativas (¡puede almacenarse directamente en el disco!). Las versiones posteriores contienen extensiones de procedimiento, lo que lo hace muy similar al núcleo de PHP. Al que le teme a Basic y PHP, las paperas le parecerán aterradoras, pero otras no. Los programadores no lo hacen. Y recuerde, es un sistema muy antiguo, todas las cosas extrañas, como las instrucciones de una letra (aunque puede usar nombres completos), el orden de evaluación de LR, etc., así como las soluciones no extrañas, tienen un solo objetivo: la optimización .
ern0
"Deberíamos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% del tiempo: la optimización prematura es la raíz de todo mal. Sin embargo, no debemos dejar pasar nuestras oportunidades en ese crítico 3%". - Donald Knuth. Lo que usted describe me parece un lenguaje heredado cuyo énfasis es la compatibilidad con versiones anteriores, y eso está bien. Personalmente, en esas aplicaciones, considero que la mantenibilidad es más importante que la optimización, y un lenguaje con expresiones no algebraicas y comandos de una letra suena contraproducente. Sirvo al cliente, no al idioma.
Mike DeSimone
@ Mike: enlaces muy entretenidos que publicaste, me reí mucho mientras los leía.
shuttle87
2

No aprendo Java, C \ C ++, Assembly y Java Script. Yo uso C ++ para ganarse la vida.

Sin embargo, tendría que decir que me gusta más la programación de ensamblaje y la programación de C. Esto está en línea principalmente con la programación imperativa.

Sé que los Paradigmas de programación son importantes para clasificar los tipos de datos y otorgar conceptos abstractos de programación más altos para permitir poderosos patrones de diseño y formalización de código. Aunque en cierto sentido, cada Paradigms es una colección de patrones y colecciones para abstraer la capa de hardware subyacente para que no tenga que pensar en el EAX o IP internamente dentro de la máquina.

Mi único problema con esto es que permite que la noción de personas y los conceptos de cómo funciona la máquina se conviertan en afirmaciones ambiguas e ideológicas de lo que está sucediendo. Este pan es todo tipo de abstracciones maravillosas además de resúmenes para algún objetivo ideológico del programador.

Al final del día, es mejor tener una buena mentalidad clara y los límites de lo que es la CPU y cómo funcionan las computadoras bajo el capó. Lo único que le importa a la CPU es ejecutar una serie de instrucciones que mueven los datos dentro y fuera de la memoria a un registro y realiza una instrucción. No tiene ningún concepto de tipo de datos, ni ningún concepto de programación superior. Solo mueve datos.

Se vuelve más complejo cuando agrega paradigmas de programación a la mezcla porque nuestra visión del mundo es completamente diferente.

Chad
fuente
2

¿hay alguna característica o idioma de lenguaje poderoso que utilice en un lenguaje que sería difícil de conceptualizar o implementar si estuviera escribiendo solo en c ++?

¿Hay algún concepto o técnica útil que haya encontrado en otros idiomas que le hubiera resultado difícil de conceptualizar si hubiera estado escribiendo o "pensando" en c ++?

C ++ hace que muchos enfoques sean intratables. Diría que la mayoría de la programación es difícil de conceptualizar si te limitas a C ++. Aquí hay algunos ejemplos de problemas que se resuelven mucho más fácilmente en formas que C ++ dificulta.

Registrar asignaciones y convenciones de llamadas

Mucha gente piensa en C ++ como un lenguaje básico de bajo nivel, pero realmente no lo es. Al abstraer detalles importantes de la máquina, C ++ dificulta la conceptualización de aspectos prácticos como la asignación de registros y las convenciones de llamadas.

Para conocer conceptos como estos, recomiendo probar la programación en lenguaje ensamblador y consultar este artículo sobre la calidad de generación de código ARM .

Generación de código en tiempo de ejecución

Si solo conoce C ++, entonces probablemente piense que las plantillas son el todo y el final de la metaprogramación. No lo son De hecho, son una herramienta objetivamente mala para la metaprogramación. Cualquier programa que manipule otro programa es un metaprograma, que incluye intérpretes, compiladores, sistemas de álgebra computacional y demostradores de teoremas. La generación de código en tiempo de ejecución es una característica útil para esto.

Recomiendo activar una implementación de Scheme y jugar EVALpara aprender sobre la evaluación metacircular.

Manipulando árboles

Los árboles están en todas partes en la programación. En el análisis tiene árboles de sintaxis abstracta. En los compiladores tienes IR que son árboles. En gráficos y programación GUI tienes árboles de escena.

Este "Analizador JSON ridículamente simple para C ++" pesa solo 484 LOC, que es muy pequeño para C ++. Ahora compárelo con mi propio analizador JSON simple que pesa solo 60 LOC de F #. La diferencia se debe principalmente a que los tipos de datos algebraicos de ML y la coincidencia de patrones (incluidos los patrones activos) hacen que sea mucho más fácil manipular los árboles.

Echa un vistazo a los árboles rojo-negros en OCaml también.

Estructuras de datos puramente funcionales.

La falta de GC en C ++ hace que sea prácticamente imposible adoptar algunos enfoques útiles. Las estructuras de datos puramente funcionales son una de esas herramientas.

Por ejemplo, vea este comparador de expresiones regulares de 47 líneas en OCaml. La brevedad se debe en gran medida al uso extensivo de estructuras de datos puramente funcionales. En particular, el uso de diccionarios con claves que son conjuntos. Eso es realmente difícil de hacer en C ++ porque los diccionarios y conjuntos stdlib son mutables, pero no puede mutar las claves de un diccionario o puede romper la colección.

La programación lógica y los búferes de deshacer son otros ejemplos prácticos en los que las estructuras de datos puramente funcionales hacen que algo difícil en C ++ sea realmente fácil en otros lenguajes.

La cola llama

C ++ no solo no garantiza las llamadas de cola, sino que RAII está fundamentalmente en desacuerdo porque los destructores se interponen en el camino de una llamada en posición de cola. Las llamadas de cola le permiten realizar un número ilimitado de llamadas de función utilizando solo una cantidad limitada de espacio de pila. Esto es excelente para implementar máquinas de estado, incluidas las máquinas de estado extensibles y es una excelente tarjeta para "salir de la cárcel" en muchas circunstancias incómodas.

Por ejemplo, revise esta implementación del problema de mochila 0-1 usando el estilo de paso de continuación con la memorización en F # de la industria financiera. Cuando tiene llamadas de cola, el estilo de paso de continuación puede ser una solución obvia, pero C ++ lo hace intratable.

Concurrencia

Otro ejemplo obvio es la programación concurrente. Aunque esto es completamente posible en C ++, es extremadamente propenso a errores en comparación con otras herramientas, sobre todo comunicando procesos secuenciales como se ve en lenguajes como Erlang, Scala y F #.

Jon Harrop
fuente
1

Esta es una vieja pregunta, pero como nadie la ha mencionado, agregaré comprensiones de listas (y ahora dict). Es fácil escribir una línea en Haskell o Python que resuelva el problema de Fizz-Buzz. Intenta hacer eso en C ++.

Si bien C ++ hizo movimientos masivos a la modernidad con C ++ 11, es un poco difícil llamarlo un lenguaje "moderno". C ++ 17 (que aún no se ha lanzado) está haciendo aún más movimientos para llegar a los estándares modernos, siempre que "moderno" signifique "no del milenio anterior".

Incluso las comprensiones más simples que uno puede escribir en una línea en Python (y obedeciendo el límite de longitud de línea de 79 caracteres de Guido) se convierten en muchas líneas de códigos cuando se traducen a C ++, y algunas de esas líneas de código C ++ son bastante complicadas.

David Hammen
fuente
Nota bien: la mayor parte de mi programación está en C ++. Me gusta el idioma
David Hammen
¿Pensé que se suponía que la propuesta Ranges resolvería esta? (Ni siquiera en C ++ 17, creo)
Martin Ba
2
"movimientos masivos hacia la modernidad": ¿Qué características "modernas" ofrece C ++ 11 que se inventaron en el milenio actual?
Giorgio
@MartinBa: entiendo que la propuesta de "rangos" es que reemplaza a los iteradores que son más fáciles de trabajar y menos propensos a errores. No he visto ninguna sugerencia de que permitirían algo tan interesante como las listas de comprensión.
Julio
2
@Giorgio: ¿qué características de cualquier lenguaje popular actualmente se inventaron en el milenio actual?
Julio
0

Una biblioteca compilada que llama a una devolución de llamada, que es una función miembro definida por el usuario de una clase definida por el usuario.


Esto es posible en Objective-C, y hace que la programación de la interfaz de usuario sea muy sencilla. Puede decirle a un botón: "Por favor, llame a este método para este objeto cuando lo presione", y el botón lo hará. Puede usar cualquier nombre de método para la devolución de llamada que desee, no está congelado en el código de la biblioteca, no tiene que heredar de un adaptador para que funcione, ni el compilador desea resolver la llamada en tiempo de compilación, e, igualmente importante, puede decirle a dos botones que llamen a dos métodos diferentes del mismo objeto.

Todavía no he visto una forma similarmente flexible de definir una devolución de llamada en ningún otro idioma (¡aunque estaría muy interesado en saber de ellos!). El equivalente más cercano en C ++ probablemente esté pasando una función lambda que realiza la llamada requerida, que nuevamente restringe el código de la biblioteca para que sea una plantilla.

Es esta característica de Objective-C la que me ha enseñado a valorar la capacidad de un lenguaje para transmitir cualquier tipo de objetos / funciones / cualquier-concepto-importante-que-el lenguaje contenga libremente, junto con el poder de salvarlos. variables Cualquier punto en un lenguaje que defina cualquier tipo de concepto, pero que no proporcione un medio para almacenarlo (o una referencia a él) en todos los tipos de variables disponibles, es un obstáculo significativo y probablemente una fuente de información muy fea, código duplicado Desafortunadamente, los lenguajes de programación barrocos tienden a exhibir varios de estos puntos:

  • En C ++ no puede escribir el tipo de un VLA, ni almacenar un puntero en él. Esto prohíbe efectivamente las matrices multidimensionales verdaderas de tamaño dinámico (que están disponibles en C desde C99).

  • En C ++ no puede escribir el tipo de lambda. Ni siquiera puedes escribirlo. Por lo tanto, no hay forma de pasar una lambda o almacenar una referencia a ella en un objeto. Las funciones de Lambda solo se pueden pasar a plantillas.

  • En Fortran no puede escribir el tipo de una lista de nombres. Simplemente no hay forma de pasar una lista de nombres a ningún tipo de rutina. Entonces, si tiene un algoritmo complejo que debería ser capaz de manejar dos listas de nombres diferentes, no tiene suerte. No puede simplemente escribir el algoritmo una vez y pasarle las listas de nombres relevantes.

Estos son solo algunos ejemplos, pero ve el punto en común: cada vez que ve una restricción así por primera vez, generalmente no le importará porque parece una idea muy loca hacer lo prohibido. Sin embargo, cuando realiza una programación seria en ese lenguaje, finalmente llega al punto en que esta restricción precisa se convierte en una verdadera molestia.

cmaster
fuente
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!) Lo que acaba de describir suena exactamente como la forma en que funciona el código de interfaz de usuario controlado por eventos en Delphi. (Y en .NET WinForms, que estuvo fuertemente influenciado por Delphi.)
Mason Wheeler
2
"En C ++ no puede escribir el tipo de VLA" [...] - en C ++, los VLA de estilo C99 son innecesarios, porque nosotros sí std::vector. Si bien es un poco menos eficiente debido a que no utiliza la asignación de la pila, es funcionalmente isomorfo a un VLA, por lo que realmente no cuenta como un problema de tipo "blub": los programadores de C ++ pueden ver cómo funciona y simplemente decir: "ah sí , C hace eso más eficientemente que C ++ ".
Julio
2
"En C ++ no se puede escribir el tipo de una lambda. Ni siquiera se puede escribir def. Lo que significa que no hay forma de pasar una lambda o almacenar una referencia en un objeto", para eso std::functionestá.
Julio
3
"Todavía no he visto una forma similarmente flexible de definir una devolución de llamada en ningún otro idioma (¡aunque estaría muy interesado en saber de ellos!"). En Java, puede escribir object::methody se convertirá en una instancia. de cualquier interfaz que el código de recepción espere. C # tiene delegados. Todo lenguaje funcional de objetos tiene esta característica porque es básicamente el punto de sección transversal de los dos paradigmas.
Julio
@Jules Sus argumentos son precisamente de lo que se trata la Blub-Paradox: como programador experto en C ++, no los ve como limitaciones. Sin embargo, son limitaciones, y otros lenguajes como C99 son más potentes en estos puntos específicos. Para su último punto: hay soluciones posibles en muchos idiomas, pero no conozco una que realmente le permita pasar el nombre de cualquier método a otra clase y que también lo llame a algún objeto que proporcione.
cmaster