O Loop é uma linguagem de programação compacta para máquina virtual Java, influenciado pelas linguagens Haskell, Scheme, Ruby e Erlang, e que tenta reunir as melhores características da programação funcional e de linguagens orientadas a objetos, de forma consistente e pragmática. Veja aqui
Com o Loop, os programas são compilados em tempo real, gerando bytecode otimizado para a JVM. Não sofrem, portanto, perda de desempenho devido à interpretação de código; ao mesmo tempo é mantida a responsividade e velocidade do esquema "editar-executar" característico do código interpretado.
A estrutura de um arquivo do Loop é a seguinte:
declaração do módulo (module)
declaração dos imports
definição de funções e tipos
expressões livres
Aqui está um exemplo de um programa em Loop:
module mymodule require othermod require yet.another class Pair -> left: 0 right: 0 main -> new Pair() # comentários podem aparecer em qualquer lugar # expressões livres devem aparecer apenas no final print('mymodule is go!')
O InfoQ.com ouviu o criador do Loop, Dhanji R. Prasanna, que é um ex-funcionário do Google e coautor da especificação JAX-RS, além de autor do livro "Injeção de Dependências: Design Patterns" pela Manning (título original "Dependency Injection: Design Patterns", não publicado no Brasil).
InfoQ: Como podemos comparar o Loop às demais linguagens para a JVM?
Dhanji: Eu diria que a filosofia do Loop é oferecer uma experiência consistente, simples e divertida na codificação. Todos os recursos são projetados com essa perspectiva abrangente em mente, além do cuidado com a forma que recursos interagem entre si sintaticamente e semanticamente.
Em outras linguagens, às vezes há várias maneiras de fazer a mesma coisa, como se isso fosse uma característica da linguagem; mas algumas dessas maneiras parecem artificiais. Já no Loop tentei diminuir o número de formas canônicas de se fazer as coisas, buscando concisão e simplicidade. O resultando foi uma sintaxe mais confortável e legível. Acho que deve ser tão fácil ler o código quanto escrevê-lo; e igualmente divertido.
Outro ponto de diferença é que o Loop compila o código diretamente para o bytecode da JVM, mas o faz em tempo real. Isso significa que se comporta e se "parece" com uma linguagem de script (no estilo REPL, como Lisp), mas é bem melhor que uma linguagem interpretada. Vou deixar, claro, que outros executem seus benchmarks, mas até agora nos meus testes rápidos, o Loop tem se mostrado extremamente veloz. Dei também muita ênfase ao tempo de inicialização; por isso o Loop inicia rápido quanto o próprio Java permite. Esse aspecto muitas vezes é ignorado por linguagens modernas para a JVM.
O Loop também se integra bem com o Java. É fácil chamar métodos ou instanciar objetos do Java a partir de um programa em Loop. Listas, conjuntos e mapas são simplesmente membros do pacote java.util, mas com várias extensões (isso vale também para Strings). Isso difere de algumas linguagens, que usam dois conjuntos de bibliotecas de dados para fornecer extensões.
Por fim, o Loop tem suporte nativo à concorrência, com imutabilidade e compartilhamento seguro de estado embutidos na linguagem.
InfoQ: Você diz que muitas das características do Loop são inspiradas por linguagens como Haskell, Scheme e Ruby. Poderia mencionar exemplos?
Dhanji: É um assunto delicado, pois quando se diz "foi inspirado em", as pessoas tendem a pensar "é uma cópia de", e terminam usando um pente-fino para procurar as diferenças. A influência maior vem da sintaxe dessas linguagens. As mais fáceis de se perceber são: correspondência de padrões (pattern matching) e os blocos "where" e "do" do Haskell; o sistema de tipos, os módulos, TCO (Tail Call Optimization) e construções léxicas (closures) do Scheme; além de ideias tais como símbolos e o scripting de forma livre do Ruby.
Um exemplo sintático dessas influências é a possibilidade de chamar funções em notação pós-fixada:
print(36) # pode ser escrito como: 36.print()
Isso torna as chamadas parecidas com as de métodos Ruby, mas na realidade são simplesmente funções polimórficas (overloaded). Acho isso muito útil para melhorar a legibilidade de alguns "tipos específicos" de código, especialmente quando "se estende" a funcionalidade existente em objetos Java. Claro, há um equilíbrio a ser alcançado, mas acredito que isso virá na medida em que o Loop amadurecer.
As influências semânticas mais profundas, do Haskell e do Scheme (dessa última em particular), também estão presentes no design funcional da linguagem. Um exemplo é o distanciamento do modelo stateful, e o design orientado ao encapsulamento para uma abordagem mais stateless e declarativa. Assim como no Scheme, o Loop também traz um "superconjunto impuro" para IO. Por outro lado, impõe a imutabilidade sempre que lida com a concorrência. Esta última é uma influência filosófica do Haskell.
Além disso, a ênfase em tornar código declarativo mais fácil de escrever (e ler) é também uma influência do Haskell. Gosto da filosofia de que o código deve ser lido como uma solução, ao invés de um amontoado de instruções; em outras palavras, enfatizando "o que" em vez de o "como" do programa. O Loop definitivamente segue esta filosofia.
InfoQ: O Loop dá muita ênfase à concorrência, além de fornecer uma abstração nativa de envio de mensagens. Poderia explicar como isso se compara a outras tecnologias populares de concorrência na JVM e além?
Dhanji: Há muito do estado da arte do Erlang nisso. Existem dois métodos principais para se fazer concorrência no Loop. Ambos foram construídos na linguagem, e também são úteis se usados em conjunto:
- Canais orientados a mensagens: uma abstração orientada a eventos sobre message-passing, filas e pools de threads trabalhadores;
- Memória transacional em software: um esquema livre de bloqueio, atômico e consistente para compartilhamento de estado mutável.
No primeiro caso, toda a abstração já é tratada para você. Pode-se definir vários "canais" leves, que podem ser configurados para que sejam executados em paralelo, para tratar um grande número de tarefas, ou para processar uma tarefa de cada vez (sequencialmente) em lotes. Isso fornece uma maneira fácil de se criar filas de eventos "fragmentadas", com naturalidade.
Uma vez que os canais são leves, pode-se criar dezenas de milhares deles sem nenhum prejuízo, e usá-los para fragmentar a execução da tarefa, por exemplo, por usuário. Há também uma memória local leve e persistente, disponível para cada canal de serialização, tornando mais fácil implementar o processamento incremental de tarefas. O Loop também garante que as threads de trabalho (worker) sejam balanceadas de forma homogênea nos canais, usando um fator configurável de "justiça" (fairness).
Tudo o que descrevi até agora já está disponível. Além disso, pode-se configurar cada canal para que tenha o seu próprio pool dedicado de threads, e assim por diante.
InfoQ: E a memória transacional?
A memória transacional é uma construção muito mais poderosa, semelhante à "concorrência otimista" utilizada em bancos de dados. A ideia é que nada nunca trava, mesmo durante a escrita. Esse tipo de memória é otimizado para taxas extremamente elevadas de leitura e escrita não-bloqueantes. Isto faz parte da própria gramática da linguagem:
update(person) in @person -> this.name: person.name, this.age: person.age
Perceba o trecho "in @person", que diz ao Loop para transacionar na célula @person.
Nesse método atualizei a "célula transacional" @pessoa com novos dados a partir do argumento passado em update(). O ponteiro "this" refere-se à célula atual, que está em uma transação ao longo da função. Quando a função for concluída, as alterações em @person serão visíveis atomicamente para todas as demais threads; ou nenhuma das alterações, se a operação falhar (semelhante a um rollback).
Todas as outras threads (mesmo as que não participem de uma transação) continuam a ver uma célula @person consistente até que a transação seja executada. Logo em seguida veem os novos dados de "person" como um todo, sem locks ou espera. O interessante aqui é que as threads de leitura e escrita nunca ficam bloqueadas.
Esse recurso está ainda um pouco alfa, pois estou tentando chegar à semântica ideal; mas sinto que estou próximo a uma API de canais rica, que torne a programação concorrente no Loop elegante, poderosa e fácil de entender.
Você pode contribuir com o Loop no Github.