Eu estava lendo o artigo aqui: http://www.paulgraham.com/avg.html e a parte sobre o "paradoxo blub" foi particularmente interessante. Como alguém que codifica principalmente em c ++, mas tem exposição a outras linguagens (principalmente Haskell), estou ciente de algumas coisas úteis nessas linguagens que são difíceis de replicar em c ++. A questão é principalmente para pessoas que são proficientes em c ++ e em alguma outra linguagem. Existe algum recurso ou idioma poderoso que você utiliza em uma linguagem que seria difícil de conceituar ou implementar se você estivesse escrevendo apenas em c ++?
Em particular, esta citação chamou minha atenção:
Por indução, os únicos programadores em posição de ver todas as diferenças de poder entre as várias linguagens são aqueles que entendem a mais poderosa. (Provavelmente, é isso que Eric Raymond quis dizer sobre Lisp fazer de você um programador melhor.) Você não pode confiar nas opiniões dos outros, por causa do paradoxo Blub: eles estão satisfeitos com o idioma que usam, porque dita o a maneira como eles pensam sobre os programas.
Se eu sou equivalente ao programador "Blub" em virtude do uso de c ++, isso levanta a seguinte pergunta: Existem conceitos ou técnicas úteis que você encontrou em outros idiomas que você teria achado difícil conceituar se você esteve escrevendo ou "pensando" em c ++?
Por exemplo, o paradigma de programação lógica visto em linguagens como Prolog e Mercury pode ser implementado em c ++ usando a biblioteca de castor, mas finalmente acho que conceitualmente estou pensando em termos de código Prolog e traduzindo para o equivalente em c ++ ao usá-lo. Como forma de ampliar meu conhecimento de programação, estou tentando descobrir se há outros exemplos semelhantes de expressões úteis / poderosas que são expressas com mais eficiência em outras linguagens que talvez eu não conheça como desenvolvedor de c ++. Outro exemplo que vem à mente é o sistema macro no lisp, gerar o código do programa a partir do programa parece ter muitos benefícios para alguns problemas. Parece ser difícil de implementar e pensar a partir do c ++.
Esta questão não pretende ser um debate "c ++ vs lisp" ou qualquer tipo de debate do tipo guerra de idiomas. Fazer uma pergunta como essa é a única maneira que eu posso ver possível para descobrir coisas que eu não sei e que não sei.
fonte
there are things that other languages can do that Lisp can't
- Improvável, já que Lisp é Turing-complete. Talvez você quisesse dizer que há algumas coisas que não são práticas para fazer no Lisp? Eu poderia dizer o mesmo sobre qualquer linguagem de programação.Respostas:
Bem, desde que você mencionou Haskell:
Correspondência de padrões. Acho a correspondência de padrões muito mais fácil de ler e escrever. Considere a definição de mapa e pense em como ele seria implementado em um idioma sem correspondência de padrões.
O sistema de tipos. Às vezes pode ser uma dor, mas é extremamente útil. Você precisa programar com ele para realmente entender e quantos erros ele pega. Além disso, a transparência referencial é maravilhosa. Após a programação em Haskell, apenas se torna aparente quantos bugs são causados pelo gerenciamento de estado em uma linguagem imperativa.
Programação funcional em geral. Usando mapas e dobras em vez de iteração. Recursão. É sobre pensar em um nível superior.
Avaliação preguiçosa. Novamente, trata-se de pensar em um nível superior e deixar o sistema lidar com a avaliação.
Cabal, pacotes e módulos. Ter os pacotes de download do Cabal para mim é muito mais conveniente do que encontrar o código-fonte, escrever um makefile etc. Ser capaz de importar apenas determinados nomes é muito melhor do que essencialmente ter todos os arquivos de origem reunidos e compilados.
fonte
Maybe
(para ver em C ++std::optional
), é preciso ter que marcar explicitamente as coisas como opcional / anulável / talvez.Memoize!
Tente escrevê-lo em C ++. Não com C ++ 0x.
Muito pesado? Ok, tente com C ++ 0x.
Veja se você consegue vencer esta versão em tempo de compilação de 4 linhas (ou 5 linhas, o que for: P) em D:
Tudo o que você precisa fazer para chamá-lo é algo como:
Você também pode tentar algo semelhante no Scheme, embora seja um pouco mais lento porque acontece em tempo de execução e porque a pesquisa aqui é linear em vez de hash (e bem, porque é o Scheme):
fonte
C ++ é uma linguagem multiparadigmática, o que significa que tenta oferecer suporte a muitas maneiras de pensar. Às vezes, um recurso C ++ é mais estranho ou menos fluente do que a implementação de outra linguagem, como é o caso da programação funcional.
Dito isso, não consigo pensar em um recurso nativo da linguagem C ++ que faça o que
yield
Python ou JavaScript faz.Outro exemplo é a programação simultânea . O C ++ 0x terá uma opinião a respeito, mas o padrão atual não, e a simultaneidade é uma maneira totalmente nova de pensar.
Além disso, o desenvolvimento rápido - mesmo a programação shell - é algo que você nunca aprenderá se nunca sair do domínio da programação C ++.
fonte
setjmp
elongjmp
. Não tenho ideia de quanto isso quebra, mas acho que as exceções seriam as primeiras a desaparecer. Agora, se você me der licença, preciso reler o Design Moderno C ++ para tirar isso da minha cabeça.As corotinas são um recurso de linguagem imensamente útil que sustenta muitos dos benefícios mais tangíveis de outras linguagens sobre o C ++. Eles basicamente fornecem pilhas extras para que as funções possam ser interrompidas e continuadas, fornecendo recursos semelhantes a pipeline para o idioma que alimenta facilmente os resultados das operações através de filtros para outras operações. É maravilhoso, e no Ruby achei muito intuitivo e elegante. A avaliação preguiçosa também está ligada a isso.
Introspecção e compilação / execução / avaliação de código de tempo de execução / o que for, são recursos extremamente poderosos que o C ++ não possui.
fonte
Tendo implementado um sistema de álgebra computacional em Lisp e C ++, posso dizer que a tarefa foi muito mais fácil no Lisp, mesmo sendo um novato completo na linguagem. Essa natureza simplista de tudo o que está sendo listado simplifica muitos algoritmos. Concedido, a versão C ++ era zilhões de vezes mais rápido. Sim, eu poderia ter feito a versão lisp mais rápida, mas o código não seria tão lispy. O script é outra coisa que sempre será mais fácil é o lisp, por exemplo. É tudo sobre como usar a ferramenta certa para o trabalho.
fonte
O que queremos dizer quando dizemos que um idioma é "mais poderoso" que outro? Quando dizemos que um idioma é "expressivo?" Ou "rico?" Acho que queremos dizer que uma linguagem ganha poder quando seu campo de visão se estreita o suficiente para tornar mais fácil e natural descrever um problema - realmente uma transição de estado, não? - que vive dentro dessa visão. No entanto, essa linguagem é consideravelmente menos poderosa, menos expressiva e menos útil quando nosso campo de visão se alarga.
Quanto mais "poderosa" e "expressiva" a linguagem, mais limitado seu uso. Então, talvez "poderoso" e "expressivo" sejam as palavras erradas para usar em uma ferramenta de utilidade restrita. Talvez "apropriado" ou "abstrato" sejam palavras melhores para essas coisas.
Comecei a programar escrevendo um monte de coisas de baixo nível: drivers de dispositivo com suas rotinas de interrupção; programas incorporados; código do sistema operacional. O código era íntimo do hardware e escrevi tudo em linguagem assembly. Não diríamos que o assembler é o menos abstrato, mas foi e é a linguagem mais poderosa e expressiva de todos eles. Eu posso expressar qualquer problema na linguagem assembly; é tão poderoso que eu posso fazer o que quiser com qualquer máquina.
E todo o meu entendimento posterior da linguagem de nível superior deve tudo à minha experiência com assembler. Tudo o que aprendi depois foi fácil porque, veja você, tudo - não importa o quão abstrato - deve, no final, acomodar-se ao hardware.
Você pode querer esquecer níveis cada vez mais altos de abstração - isto é, campos de visão cada vez mais estreitos. Você sempre pode pegar isso mais tarde. É muito fácil aprender, em questão de dias. Na minha opinião, seria melhor você aprender a linguagem do hardware 1 , chegar o mais perto possível do osso.
1 Talvez não completamente pertinente, mas
car
ecdr
tirar seus nomes do hardware: o primeiro Lisp correu em uma máquina que tinha um Decrement Register real e um endereço real Register. Que tal isso?fonte
Matrizes associativas
Uma maneira típica de processar dados é:
A ferramenta certa para isso é a matriz associativa .
Eu realmente não gosto da sintaxe associativa do JavaScript, porque não posso criar, digamos a [x] [y] [z] = 8 , primeiro tenho que criar a [x] e a [x] [y] .
Ok, em C ++ (e em Java) há um bom portfólio de classes de contêineres, Map , Multimap , qualquer que seja, mas se eu quiser varrer, preciso fazer um iterador e, quando quero inserir um novo elemento de nível profundo, eu tem que criar todos os níveis superiores etc. Desconfortável.
Não digo que não haja matrizes associativas utilizáveis em C ++ (e Java), mas as linguagens de script sem tipo (ou de tipo não estrito) superam as compiladas, porque são linguagens de script sem tipo.
Disclaimer: Eu não estou familiarizado com C # e outros lances de .NET, AFAIK eles têm um bom manuseio associativo de array.
fonte
dict
tipo interno (por exemplox = {0: 5, 1: "foo", None: 500e3}
, observe que não há requisitos para que chaves ou valores sejam do mesmo tipo). Tentar fazer algo assima[x][y][z] = 8
é difícil, porque a linguagem precisa olhar para o futuro para ver se você deseja definir um valor ou criar outro nível; a expressãoa[x][y]
por si só não diz a você.Eu não aprendo Java, C \ C ++, Assembly e Java Script. Eu uso C ++ para ganhar a vida.
No entanto, eu diria que gosto mais de programação Assembly e programação C. Isso está alinhado principalmente com a programação Imperative.
Eu sei que os paradigmas de programação são importantes para categorizar os tipos de dados e fornecem conceitos abstratos de programação mais elevados para permitir poderosos padrões de design e formalização de código. Embora, de certa forma, cada Paradigms seja uma coleção de padrões e coleções para abstrair a camada de hardware subjacente, para que você não precise pensar no EAX ou IP internamente na máquina.
Meu único problema com isso é que permite que a noção e os conceitos das pessoas de como a máquina funciona se transforme em ideologias e afirmações ambíguas do que está acontecendo. Este pão é todo tipo de abstrações maravilhosas, além de abstratos, para algum objetivo ideológico do programador.
No final do dia, é melhor ter uma boa mentalidade e limites claros do que é a CPU e como os computadores funcionam sob o capô. Todo o CPU se preocupa em executar uma série de instruções que movem os dados para dentro e para fora da memória em um registro e executam uma instrução. Não possui um conceito de tipo de dados ou qualquer conceito de programação superior. Ele apenas move os dados.
Torna-se mais complexo quando você adiciona paradigmas de programação à mistura, porque nossa visão do mundo é diferente.
fonte
O C ++ torna muitas abordagens intratáveis. Eu diria que a maior parte da programação é difícil de conceituar se você se limitar ao C ++. Aqui estão alguns exemplos de problemas que são muito mais facilmente resolvidos de maneiras que o C ++ dificulta.
Registre convenções de alocação e chamada
Muitas pessoas pensam em C ++ como uma linguagem de baixo nível bare metal, mas na verdade não é. Ao abstrair detalhes importantes da máquina, o C ++ torna difícil conceituar aspectos práticos, como alocação de registros e convenções de chamada.
Para aprender sobre conceitos como esses, recomendo dar uma olhada em alguma programação em linguagem assembly e confira este artigo sobre a qualidade da geração de código ARM .
Geração de código em tempo de execução
Se você conhece apenas C ++, provavelmente pensa que os modelos são o princípio e o fim da metaprogramação. Eles não são. De fato, eles são uma ferramenta objetivamente ruim para a metaprogramação. Qualquer programa que manipule outro programa é um metaprograma, incluindo intérpretes, compiladores, sistemas de álgebra computacional e provadores de teoremas. A geração de código em tempo de execução é um recurso útil para isso.
Eu recomendo
EVAL
iniciar uma implementação do Scheme e brincar para aprender sobre a avaliação metacircular.Manipulando árvores
Árvores estão por toda parte na programação. Na análise, você tem árvores de sintaxe abstratas. Nos compiladores, você tem IRs que são árvores. Em gráficos e programação GUI, você tem árvores de cena.
Este "Analisador JSON Ridiculamente Simples para C ++" pesa apenas 484 LOC, o que é muito pequeno para C ++. Agora compare-o com meu próprio analisador JSON simples, que pesa apenas 60 LOC de F #. A diferença é principalmente porque os tipos de dados algébricos do ML e a correspondência de padrões (incluindo padrões ativos) facilitam muito a manipulação de árvores.
Confira também árvores vermelho-pretas no OCaml .
Estruturas de dados puramente funcionais
A falta de GC no C ++ torna praticamente impossível adotar algumas abordagens úteis. Estruturas de dados puramente funcionais são uma dessas ferramentas.
Por exemplo, confira este correspondente de expressão regular de 47 linhas no OCaml. A brevidade se deve em grande parte ao uso extensivo de estruturas de dados puramente funcionais. Em particular, o uso de dicionários com chaves que são conjuntos. Isso é realmente difícil de fazer em C ++ porque os dicionários e conjuntos stdlib são todos mutáveis, mas você não pode alterar as chaves de um dicionário ou interromper a coleção.
A programação lógica e os buffers de desfazer são outros exemplos práticos em que estruturas de dados puramente funcionais tornam algo difícil em C ++ muito fácil em outras linguagens.
Chamadas de cauda
O C ++ não apenas não garante chamadas de cauda, mas o RAII está fundamentalmente em desacordo com isso, porque os destruidores impedem uma chamada na posição de cauda. As chamadas finais permitem que você faça um número ilimitado de chamadas de função usando apenas uma quantidade limitada de espaço na pilha. Isso é ótimo para a implementação de máquinas de estado, incluindo máquinas de estado extensíveis, e é um ótimo cartão "saia da cadeia" em muitas circunstâncias que de outro modo seriam embaraçosas.
Por exemplo, confira esta implementação do problema da mochila 0-1 usando o estilo de passagem de continuação com memorização em F # do setor financeiro. Quando você tem chamadas de chamada, o estilo de passagem de continuação pode ser uma solução óbvia, mas o C ++ o torna intratável.
Concorrência
Outro exemplo óbvio é a programação simultânea. Embora isso seja inteiramente possível em C ++, é extremamente propenso a erros em comparação com outras ferramentas, principalmente na comunicação de processos sequenciais, como visto em linguagens como Erlang, Scala e F #.
fonte
Esta é uma pergunta antiga, mas como ninguém a mencionou, adicionarei compreensões de lista (e agora ditarei). É fácil escrever um one-liner em Haskell ou Python que resolva o problema do Fizz-Buzz. Tente fazer isso em C ++.
Embora o C ++ tenha feito movimentos massivos para a modernidade com o C ++ 11, é um pouco exagerado chamá-lo de linguagem "moderna". O C ++ 17 (que ainda não foi lançado) está fazendo mais movimentos para chegar aos padrões modernos, desde que "moderno" signifique "não do milênio anterior".
Mesmo as compreensões mais simples que se pode escrever em uma linha em Python (e obedecendo ao limite de comprimento de linha de 79 caracteres de Guido) tornam-se muitas e muitas linhas de códigos quando traduzidas para C ++, e algumas dessas linhas de código C ++ são bastante complicadas.
fonte
Uma biblioteca compilada chamando um retorno de chamada, que é uma função de membro definida pelo usuário de uma classe definida pelo usuário.
Isso é possível no Objective-C e facilita a programação da interface do usuário. Você pode dizer um botão: "Por favor, chame este método para este objeto quando você pressionar", e o botão fará isso. Você pode usar qualquer nome de método para o retorno de chamada que desejar, não está congelado no código da biblioteca, não deve herdar de um adaptador para que ele funcione, nem o compilador deseja resolver a chamada em tempo de compilação, e, igualmente importante, você pode dizer a dois botões para chamar dois métodos diferentes do mesmo objeto.
Ainda não vi uma maneira igualmente flexível de definir um retorno de chamada em qualquer outro idioma (embora eu estivesse muito interessado em saber sobre eles!). O equivalente mais próximo em C ++ provavelmente está passando uma função lambda que executa a chamada necessária, que novamente restringe o código da biblioteca a ser um modelo.
É esse recurso do Objective-C que me ensinou a valorizar a capacidade de uma linguagem de transmitir qualquer tipo de objeto / função / qualquer conceito importante que a linguagem contenha livremente, juntamente com o poder de salvá-los. variáveis. Qualquer ponto em uma linguagem que define qualquer tipo de conceito, mas não fornece um meio para armazená-lo (ou uma referência a ele) em todos os tipos de variáveis disponíveis, é um obstáculo significativo e provavelmente uma fonte de muito feio, código duplicado. Infelizmente, as linguagens de programação barrocas tendem a exibir vários desses pontos:
No C ++, você não pode anotar o tipo de um VLA, nem armazenar um ponteiro para ele. Isso proíbe efetivamente matrizes multidimensionais verdadeiras de tamanho dinâmico (que estão disponíveis em C desde C99).
No C ++, você não pode anotar o tipo de uma lambda. Você nem pode digitar isso. Portanto, não há como passar por um lambda ou armazenar uma referência a ele em um objeto. As funções do Lambda podem ser passadas apenas para modelos.
No Fortran, você não pode anotar o tipo de uma lista de nomes. Simplesmente não há meios de passar uma lista de nomes para qualquer tipo de rotina. Portanto, se você tiver um algoritmo complexo que possa lidar com duas listas de nomes diferentes, estará sem sorte. Você não pode simplesmente escrever o algoritmo uma vez e passar as listas de nomes relevantes para ele.
Estes são apenas alguns exemplos, mas você vê o ponto comum: sempre que você vê essa restrição pela primeira vez, geralmente não se importa, porque parece uma idéia tão louca fazer a coisa proibida. No entanto, quando você faz alguma programação sincera nessa linguagem, acaba chegando ao ponto em que essa restrição precisa se torna um verdadeiro incômodo.
fonte
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!)
O que você acabou de descrever soa exatamente como o código de interface do usuário orientado a eventos funciona no Delphi. (E em WinForms, que foi fortemente influenciada pela Delphi.)std::vector
. Embora seja um pouco menos eficiente devido ao não uso da alocação de pilha, é funcionalmente isomórfico para um VLA, portanto não conta realmente como um problema do tipo "blub": os programadores de C ++ podem ver como ele funciona e apenas dizer "ah sim , C faz isso com mais eficiência do que C ++ ".std::function
é para isso.object::method
e ele será convertido em uma instância de qualquer interface que o código receptor espere. C # tem delegados. Toda linguagem funcional de objetos tem esse recurso porque é basicamente o ponto de seção dos dois paradigmas.