- As características da linguagem e o ecossistema de OCaml são excelentes e adequados tanto para projetos pessoais quanto profissionais
- Sistema de tipos estático, tipos algébricos, sistema de módulos, modelo de objetos, efeitos definidos pelo usuário e outros recursos avançados e multiparadigma estão integrados de forma estável
- Há uma cadeia de ferramentas madura, com o gerenciador de pacotes OPAM, o sistema de build Dune, suporte de editor com LSP/Merlin e a ferramenta de documentação Odoc, além de um ecossistema variado de bibliotecas para web, blockchain, tooling e mais
- A comunidade combina acessibilidade, cordialidade e especialização, o que facilita o aprendizado e a colaboração, e as perspectivas futuras também são positivas graças à evolução contínua
Por que escolhi OCaml como linguagem principal
- O autor usou várias linguagens de programação por muito tempo e, entre elas, escolheu OCaml como sua linguagem principal
- Entre os maiores pontos fortes de OCaml, destaca o forte sistema de tipos estático e o excelente suporte à programação funcional em comparação com C ou outras linguagens funcionais
- Graças a esse sistema de tipos, teve muitas experiências de prevenção de bugs e otimização de código
- Na prática, ao usar OCaml em vários projetos de desenvolvimento, experimentou uma grande melhora em produtividade e estabilidade
Vantagens do OCaml e uso prático
- A maior parte do código pode ser escrita rapidamente, e o uso de composição de funções e dados imutáveis aumenta a segurança
- Recentemente, o ecossistema e as ferramentas de OCaml (IDE, sistema de build etc.) também vêm evoluindo de forma contínua
- Graças a várias bibliotecas e pacotes externos, o desenvolvimento eficiente no trabalho se torna viável
- Em comparação com Python e Java, OCaml é menos conhecido, mas é uma opção muito forte em termos de produtividade, segurança e flexibilidade
Características da linguagem
- A origem acadêmica combinada com a aplicação industrial impulsionou a evolução de recursos centrados em expressividade e segurança
- Recursos recentes como efeitos definidos pelo usuário e sessões affine
- A verificação estática de tipos funciona tanto como rede de segurança quanto como ferramenta de design, desfazendo mal-entendidos causados por experiências ruins com tipos
- Multiparadigma: funcional, imperativo, modular, orientado a objetos e suporte a multicore
- A sintaxe da família ML é concisa e consistente, e também existem sintaxes alternativas como ReasonML
- Tipos algébricos (produto, soma e exponenciais), pattern matching e polimorfismo oferecem grande força para modelagem de dados e domínios
- O sistema de módulos oferece separação entre interface e implementação, abstração, reutilização e até polimorfismo avançado
- Inversão de dependência: fornece formas flexíveis de injeção por meio de módulos/efeitos
Ecossistema e tooling
- Alvos de compilação: nativo, bytecode, JavaScript(
Js_of_ocaml,Melange), WebAssembly - MirageOS oferece uma disciplina para escrever bibliotecas de multicontexto
- OCaml Platform:
- OPAM: gerenciamento de versões, switches, índice de pacotes e suporte a CI
- Dune: builds rápidos, configuração em S-expressions e simplificação de distribuição com
dune-release - LSP/Merlin: autocompletar, navegação e formatação de código em VSCode, Emacs etc.
- Odoc: suporte a referências cruzadas, páginas manuais, doctest etc.
- Bibliotecas ricas: web (Dream, Ocsigen), blockchain e criptografia (HACL*), testes (alcotest, qcheck etc.)
- A biblioteca padrão é pequena, mas há alternativas como Batteries, Base/Core e Containers
Novos desafios e comunidade
- A comunidade de OCaml é pequena, mas continua crescendo, e mostra uma orientação amigável ao usuário
- Para desenvolvedores que querem o desafio de uma nova linguagem ou paradigma, OCaml vale a pena ser aprendido em profundidade
- Muitos usuários afirmam que a experiência com OCaml amplia a visão e a capacidade de resolver problemas
Conclusão
- OCaml é uma linguagem de programação poderosa que pode ser usada de forma geral, não ficando limitada a áreas específicas (por exemplo, finanças, compiladores, desenvolvimento de sistemas)
- A eficiência, a manutenibilidade e a capacidade de evitar problemas obtidas na prática comprovam seu valor no trabalho real
- Mesmo sendo um pouco menos conhecida do que linguagens ou tendências mais recentes, se você prioriza confiabilidade e segurança, é uma opção que vale muito considerar
2 comentários
Eu já lidei com OCaml na pós-graduação, mas o ecossistema é realmente muito fraco, quase não há referências e, principalmente, não tem ninguém a quem perguntar. Pelo meu critério pessoal, na Coreia praticamente ninguém usa isso fora do pessoal da área de linguagens de programação na academia. Até COBOL o pessoal já deve ter ouvido falar, mas OCaml provavelmente não.
Comentários do Hacker News
Já vi uma apresentação sobre a experiência de introduzir Rust na equipe de Android do Google. Duas coisas me chamaram a atenção: como vários projetos foram migrados de Python para Rust, desempenho provavelmente não era uma questão tão grande assim, e os recursos de que os usuários de Rust mais gostavam eram coisas básicas como pattern matching e ADT (Algebraic Data Types). Então tive a impressão de que a maior contribuição real do Rust não vinha tanto de recursos próprios como lifetimes, mas de elementos que linguagens ML dos anos 1990 já ofereciam. Se o OCaml tivesse resolvido por volta de 2010 inconvenientes como multicore, acho que poderia ter ficado tão popular quanto Rust. Infelizmente, o OCaml caiu no vão entre academia e indústria. Só acrescentando uma coisa: inteiros de 31 bits são pouco práticos para operações de bits, e esteticamente eu realmente não gostava do ponto e vírgula duplo
Acho que o OCaml já estava em um estado bem razoável naquela época. Em 2010 eu o usei profissionalmente de forma muito mais agradável do que Python. Basta ver o que a JaneStreet conseguiu realizar. Na minha visão, o maior motivo de o OCaml não ter sido amplamente adotado é que ele não foi criado nem liderado pelos EUA. A gente gostaria de acreditar que a popularidade de uma linguagem vem da superioridade técnica, mas no fim é uma questão de moda. O sucesso popular do Rust também se deveu a uma quantidade enorme de divulgação e a uma comunidade muito ativa. Havia até pessoas dedicadas a promover o nome da linguagem
O Google tenta manter o mais curta possível a lista oficial de linguagens que podem ser usadas em código de produção real. Parece que Rust foi escolhido por ser uma linguagem capaz de substituir ou complementar C++. Seria difícil o OCaml ocupar essa posição (talvez pudesse substituir Go, mas isso parece improvável). Então o principal motivo para Rust ter sido escolhido provavelmente foi ser a única linguagem oficial que oferecia ADTs, e não porque velocidade de build não importasse. Também é natural que o OCaml não tenha substituído Rust. Linguagens com GC já existiam em várias opções, como Go e Haskell, e por volta de 2010 a única linguagem com expressividade suficiente para mirar bare metal era C++ (e ainda assim C++ era ainda pior antes de C++11 e C++17)
Concordo totalmente. Se o OCaml tivesse resolvido alguns probleminhas, poderia mesmo ter se tornado um player importante. A velocidade de build ainda hoje é muito melhor do que a do Rust. Só que o OPAM (gerenciador de pacotes) vive tendo bugs e é famoso por ser confuso. O suporte a Windows é absurdamente ruim, pior até do que o suporte a Windows do Perl antigamente. A documentação oficial é tão concisa que chega a ser inútil. A própria sintaxe é difícil de pegar, e mensagens do tipo “metade do arquivo tem erro de sintaxe” por causa de um typo bobo aparecem com frequência. A sintaxe estilo C já conhecida do Rust é bem mais fácil. Resumindo, a vantagem do OCaml é builds rápidos, mas isso sozinho não basta para justificar o uso
Por isso, quando quero programar em estilo ML, procuro primeiro Kotlin, Scala ou F# em vez de Rust. E hoje em dia até Java e C# já adotaram elementos de ML suficientes para eu não ter grande resistência. Estou acostumado com o sistema de tipos de ML desde a época do Caml Light e do Objective Caml, então quando vejo o entusiasmo atual em torno de Rust, parece até que as pessoas acham que Rust trouxe o sistema de tipos de ML como se fosse novidade
Sobre a ideia de que seria bom se o OCaml tivesse se preparado melhor, na prática acho que a grande vantagem é justamente haver várias opções de linguagem. Só no Reino Unido, mesmo com população menor, há uma convivência enorme de linguagens. Por exemplo, o córnico, uma língua morta da Europa, foi revivido recentemente por moradores locais, e entre pastores ainda resta o kubrick, uma linguagem para contar números. Eu mesmo comecei a usar um programa chamado Geneweb, baseado em OCAML, para a árvore genealógica da próxima geração (migrando de um app de Windows chamado TMG). Meus dados de família têm 140 mil pessoas. O fato de o Geneweb ter sido feito em OCAML aumentou meu interesse pela linguagem. Se uma linguagem de programação parecer difícil, recomendo tentar genealogia: logo o padrão GEDCOM vai te dar dor de cabeça
OCaml é uma das linguagens que eu amo. Meu trabalho mais substancial foi implementar 100% um app CRUD para a organização de um Writer's Festival usando OCaml (JSX baseado em ReasonML), Dream, HTMX e DataTables. Reaproveitei templates de frontend com módulos, e quando havia mudanças no modelo de dados eu adorava como o compilador mostrava imediatamente onde tudo quebrava. Também consegui migrar dados de planilhas Excel para um banco de dados de verdade, exportar cronogramas baseados em template no formato .odt e até gerar arquivos zip diretamente, sem passar pelo disco do servidor. Consegui fazer surpreendentemente muita coisa no ecossistema OCaml. Mas ter que escrever todas as queries do banco como strings e fazer conversão de tipos manualmente era exaustivo (sem checagem de tipos em tempo de compilação). Também precisei implementar o sistema de autenticação por conta própria, então acabava gastando tempo demais em coisas que não eram o desenvolvimento do produto principal. Depois de olhar várias linguagens, cheguei à conclusão de que linguagem perfeita não existe. Todas têm suas desvantagens peculiares. Agora estou fazendo um app pessoal em Rails, e como quase tudo de que preciso já vem por padrão, fico muito mais satisfeito porque posso focar no trabalho de fato, como layout e deploy real, em vez da linguagem em si
O DarkLang foi inicialmente desenvolvido em OCaml e depois migrou para F#. O principal motivo foram o ecossistema de bibliotecas e a concorrência (post relacionado). Estou acostumado com .NET, então talvez eu tenha certo viés, mas as partes chatas já contam com boas opções, o que permite focar nos problemas essenciais. Tenho bastante experiência profissional com F# e até mantenho uma biblioteca de UI popular, mas como o ecossistema da linguagem é pequeno, mesmo no .NET as soluções nem sempre aparecem de imediato. Por isso, vale lembrar que escolher uma linguagem fora do mainstream (por exemplo, F# em vez de C#) tem custo. Com OCaml acontece a mesma coisa: a linguagem é forte, mas por estar fora do mainstream traz várias inconveniências. Algumas empresas até a usam em produção, mas são casos ajustados às necessidades muito específicas delas
Tentei gostar de OCaml por alguns anos, e a parte mais incômoda para mim foi “não poder dar print em um objeto arbitrário”. Dá para derivar automaticamente funções
to_stringcom ppx, mas configurar isso é chato e a usabilidade fica bem abaixo do Rust. Para imprimir tipos como Set e Map também é preciso trabalho extra (exemplo de referência). Em golang, a formatação"%v"permite imprimir quase qualquer coisa com facilidade; nesse ponto, OCaml exige bem mais esforço%vdo Go também não é perfeita, e para percorrer ponteiros mais a fundo ainda é preciso uma biblioteca separada como go-spew. A abordagem__repr__do Python é a mais confortável que já vi até hojeNunca usei OCaml diretamente, mas trabalhar com F# foi uma experiência muito agradável. Nesta era de LLMs, acho que valeria a pena voltar a prestar atenção em linguagens funcionais. Em paradigmas funcionais como OCaml e Haskell, dá para comprimir informação de forma eficiente em pouco texto, então talvez seja possível colocar mais significado dentro da janela de contexto de um LLM. Talvez também valha experimentar se mudanças mais complexas podem ser aplicadas de uma vez só, em comparação com Java, C# ou Ruby
Eu também achava isso no começo, mas mudei de opinião depois de trabalhar em uma codebase grande em Haskell. Talvez por haver pouco FP no dataset de treino, linguagens mais concisas acabam combinando pior com LLMs. Quando o código é verboso, o LLM parece ter mais oportunidades de se corrigir depois de prever tokens errados, o que dá a sensação de gerar código mais correto
Nos meus experimentos pessoais, fiz um jogo simples de CLI em C++ e Haskell. Haskell teve menos linhas, mas o número de palavras foi quase o mesmo, então o código só “parece mais largo”. Não comparei com Java ou linguagens mais explícitas, mas acho que o estilo adequado varia conforme a natureza do programa. Em alguns casos o estilo imperativo é melhor; em outros, o funcional pode ser mais apropriado
Se a geração de código por LLM melhorar só mais um pouco, eu adoraria poder restringir o escopo de comportamento do código com sistemas de tipos e de efeitos realmente fortes. Por exemplo, com tipos dependentes seria possível verificar em tempo de compilação condições como “esta função sempre retorna uma lista ordenada” ou “esta função sempre retorna uma solução válida de sudoku”. Se somarmos a isso um sistema de efeitos, também seria possível dizer “esta função retorna uma solução válida de sudoku, mas não acessa a rede nem o sistema de arquivos”. Se os LLMs evoluírem mais, talvez até consigam fazer algo assim em Python, mas se o avanço for lento, acho que o futuro pode estar em usar LLMs pouco confiáveis envolvidos por sistemas determinísticos altamente confiáveis
Ao usar cats-effect (biblioteca de efeitos) em Scala, a ajuda de um LLM acelerou absurdamente meu desenvolvimento. Código com cats-effect muitas vezes faz até conceitos simples parecerem difíceis, mas basta perguntar ao LLM “como fazer X em cats-effect?” e em 80% dos casos ele já resolve. Nos 20% restantes, é só dar mais contexto. Em termos de manutenção, ainda estou testando, mas a frustração com programação funcional baseada em efeitos diminuiu bastante. Da próxima vez quero experimentar o quão bem o Claude Code se sai
Haskell tem duas grandes vantagens para geração de código por LLM. Primeiro, seu sistema de tipos altamente expressivo pega muitos erros, e o próprio erro de compilação pode ser devolvido ao LLM como feedback. Segundo, é fácil melhorar o código com eficiência e precisão usando testes baseados em propriedades (QuickCheck etc.). O LLM não escreve tão bem os testes por conta própria, mas se você os adicionar manualmente, os bugs do código gerado aparecem rapidamente
Depois de ler este texto, para mim ficou encerrada a pergunta “por que não usar F# em vez de OCaml?”. Em quase toda thread sobre OCaml aparece a sugestão “usar F# não resolveria os problemas de ferramentas?”. Eu também tinha curiosidade sobre OCaml e me interessei ao ver o apelido “Go with types”, mas o OCaml em si ainda não me parece totalmente atraente. Há algo diferente do entusiasmo que existe em comunidades de outras linguagens, como Erlang, Ruby, Rust ou Zig
Eu fui justamente na direção contrária e migrei para OCaml para evitar o ecossistema de ferramentas do F#. Na época em que usei F#, havia muitos problemas de tooling: compilador lento, ecossistema concentrado em C#, MSBuild fraco e mal documentado, Ionide quebrando o tempo todo e Fantomas pouco confiável. Dito isso, OCaml também não substitui tudo o que o F# oferece em recursos voltados a desempenho (por exemplo, value types e outras partes que o CLR suporta). Nesse sentido, ainda não encontrei uma linguagem ML simples ideal. Tenho esperança de que iniciativas como OxCaml possam resolver isso no futuro
Ultimamente não tenho usado muito OCaml, mas o núcleo da linguagem continua sendo o meu favorito. Meu estilo de código tende a se concentrar em uma única função grande, e no OCaml eu acabo evitando isso naturalmente. Tenho usado Rust em projetos paralelos, mas na verdade OCaml me parece mais confortável. Por esse motivo, também quero muito experimentar F# um dia
Tenho uma dúvida de terminologia: no artigo, tipos de função são chamados de “tipos exponenciais”, mas não entendo bem por que tipos de função de ordem superior recebem esse nome
Já houve boas explicações, mas a razão mais profunda é que tipos de função obedecem algebricamente às leis dos expoentes. Por exemplo,
A → (B → C)é isomorfo a(A × B) → Cpor currying. Isso é análogo a(cᵇ)ᵃ = cᵇ˙ᵃ. E(A + B) → Cé isomorfo a(A → C) × (B → C), o que corresponde à regracᵃ⁺ᵇ = cᵃ·cᵇTipos de função de primeira ordem já são exponenciais. Por exemplo, um sum type tem tantos valores quanto o número de casos. (Ex.:
A of bool | B of bool→2+2=4possibilidades). Product types e tipos exponenciais seguem a mesma lógica. Embool -> bool, por exemplo, existem2^2 = 4valores possíveis (desconsiderando efeitos colaterais)Normalmente, quando se fala em ADT (Algebraic Data Type), só se fala de sum e product. Funções não costumam ser mencionadas porque não são dados. Mas um tipo
a -> btemb^acasos possíveis, então dá para abordá-lo da mesma formaEu tinha a mesma dúvida, mas matematicamente, depois de soma (sum) e produto (product), vem expoente (exponent), então imagino que o nome venha dessa analogia
Todas as respostas estão corretas, mas na verdade em teoria das categorias tipos de função são chamados de “exponential product”. O nome também vem do fato de que o número de funções de A para B é calculado como a cardinalidade de B elevada à cardinalidade de A
Como os casos de um sum type são valores (expressions) por meio de type constructors, é natural que tenham tipo. Por exemplo,
cada caso recebe um tipo. Graças ao pattern matching, os parâmetros do construtor de tipo já são desempacotados diretamente. Se você extrair cada caso como um tipo separado, perde a vantagem de exhaustiveness (prevenção de casos omitidos) que o sum type oferece, e passa a representar estados inválidos do programa. Um sum type é declarado uma vez e usado várias vezes, e normalmente é descartável. A legibilidade do código também importa, e a verbosidade às vezes é subestimada. Aliás, C#/Java não oferecem suporte a sum types reais. No exemplo abaixo, dá para ver como C# fica desnecessariamente complexo por causa da abordagem OOP
Em ML isso fica muito mais conciso
As duas abordagens são quase equivalentes, mas os elementos de OOP do C# acabam atrapalhando
Em OCaml, também dá para usar GADT, Polymorphic Variants etc. para tratar cada caso como um tipo separado. Mas em geral, separar um sum type dificulta tanto a generalização quanto a compreensão. Isso também traz questões de igualdade de tipos e variance
Não entendo por que tanta discussão entre sum types e sealed types. Eu prefiro linguagens funcionais, mas graças à distinção no nível de tipos, dá para modelar todos os sum types só com sealed types, e a presença de subtyping também pode facilitar tanto a definição quanto o uso. Os paradigmas do sistema são bem diferentes, mas matematicamente são quase equivalentes, e quase toda “brincadeira com tipos” possível em OOP e FP pode ser implementada dentro do que a linguagem permitir
Não concordo que a verbosidade na declaração de sum types em Java/Kotlin seja um preço que valha a pena pagar. Para mim, parece só boilerplate típico de linguagens da JVM
Seria bom ver alguém que conhecesse bem a sintaxe do ReasonML comparar melhor os prós e contras. (O artigo menciona isso bem de passagem)
O que mais me fez falta foi let binding (documentação oficial). No ReasonML, era fácil lidar com operadores customizados como
>>=para monads. O rescript (fork do ReasonML) ainda não tem isso. Em compensação, ele dá um bom suporte a async/await, o que ajuda com código assíncrono. O Melange (mencionado rapidamente no artigo) suporta let binding na sintaxe Reason. Por isso, o Reason ML do Melange é muito vantajoso para frontend com React. Graças ao let binding, junto com JSX, dá para escrever código assíncrono em estilo monádico de forma elegante. Na sintaxe de OCaml, até dá para contornar com PPX, mas o highlighting no editor não funciona tão bem. Pensando em backend, eu gosto do estilo Python, então as chaves ainda me incomodam, e prefiro chamadas e definições de função sem parênteses. Mas como iniciante em OCaml, ainda acho confuso quando preciso usar argumentos passados sem variável, então isso continua sendo difícil para mim. Espero que esse relato ajudeUsei muito pouco ReasonML, então nunca senti muito suas vantagens. Tirando o fato de que ele morreu duas vezes em quatro anos...
Eu gostaria que a sintaxe Reason fosse mais difundida, mas se quiser se comunicar com a comunidade OCaml, é melhor simplesmente aprender a sintaxe padrão. A maioria do código e da documentação usa a sintaxe padrão, então no fim você vai precisar conhecê-la de qualquer jeito
O que mais me incomodou no ReasonML que usei foi que o LSP não funcionava direito
Gostaria que a parte sobre implementar dependency injection com um effects system fosse explicada com mais detalhes. A ideia de fazer binding de valores de teste e de produção via pattern matching parece interessante, mas só pelo texto eu não consigo visualizar bem. E também foi a primeira vez que ouvi dizer que o sistema de módulos tem seu próprio sistema de tipos