- Janet é um pequeno dialeto de Lisp, mas permite começar de forma simples por ter linguagem imperativa, funções de primeira classe, namespace único de identificadores e escopo léxico de bloco
- A linguagem central é composta por apenas 8 comandos:
do, def, var, set, if, while, break, fn; macros criam wrappers de fluxo de controle mais poderosos ou convenientes
- A distribuição é resolvida compilando programas Janet em executáveis nativos com linkagem estática com o runtime do Janet, sem exigir que o usuário instale Janet ou dependências
- A gramática de expressões de parsing (PEG) é mais simples, poderosa e previsível que regex, e sh permite expressar pipes e redirecionamentos dentro do Janet, ampliando o alcance para escrever CLIs
- A execução em tempo de compilação primeiro executa comandos de topo e depois salva um snapshot do estado do programa em disco, permitindo passar valores, referências compartilhadas, geradores e estado de closures para o runtime mesmo sem macros
Núcleo simples
- Janet é uma linguagem imperativa com funções de primeira classe, namespace único de identificadores e escopo léxico de bloco
- O núcleo da linguagem é mantido pequeno com 8 comandos:
do, def, var, set, if, while, break, fn
- Macros permitem wrappers de fluxo de controle de alto nível mais poderosos ou convenientes
- A semântica de runtime é familiar, e o restante da linguagem também é pequeno o bastante para que toda a biblioteca padrão caiba em uma única página
Distribuição nativa e embedding
- Programas Janet podem ser compilados facilmente em executáveis nativos com linkagem estática do runtime do Janet
- Usuários que recebem a distribuição não precisam instalar Janet, dependências do projeto nem qualquer outro componente separado
- Janet compila a si mesmo para bytecode, grava esse bytecode dentro de um arquivo
.c que inicializa o runtime do Janet e compila esse arquivo C com o compilador C do sistema
- Um binário nativo simples de “hello world” fica abaixo de 1 MB; no Janet 1.27.0 para aarch64 macOS, o tamanho era 784K
- Esse binário inclui todo o runtime do Janet, coletor de lixo e até o compilador de bytecode, então também é possível criar programas que avaliam código Janet em runtime
- O runtime do Janet é uma pequena biblioteca em C, então, após fazer o link, é possível manipular valores Janet chamando funções C normais
- Também pode ser embutido em sites, permitindo criar sites estáticos com DSLs programáveis personalizados, como o Toodle
Parsing de texto e DSL de subprocessos
- O processamento de texto no Janet é baseado em gramática de expressões de parsing, em vez de regex
- Gramáticas de expressões de parsing são mais simples, poderosas e previsíveis que regex, e não ficam presas a linhas, então podem analisar textos com múltiplas linhas
- Elas podem analisar HTML, JSON e outras linguagens não regulares, além de lidar com formatos de arquivo binário com bytes nulos arbitrários
- sh é uma DSL de shell scripting de terceiros que permite expressar pipes e redirecionamentos diretamente dentro de código Janet
($ find . -name *.janet | say)
- Essa DSL eleva o Janet de uma alternativa razoável ao Perl para uma alternativa razoável ao Bash em uma faixa bastante ampla de programas
Coleções e sensação da sintaxe
- Os tipos de coleção do Janet têm formas mutáveis e imutáveis
- Coleções imutáveis têm semântica de valor, então o vetor imutável
[1 2] não se distingue de (take 2 [1 2 3]), mesmo que os endereços de memória sejam diferentes
- Coleções mutáveis têm semântica de referência, então a hash table
@{:x 1 :y 2} é igual apenas a si mesma, e outra hash table com as mesmas chaves e valores é um objeto distinto
- A sintaxe usa muitos parênteses, mas diferencia as formas usando
[] para listas e {} para tabelas
- Literais mutáveis sempre recebem o prefixo
@, como em @"mutable string"
- Funções anônimas são escritas como
(fn [x] (+ 1 x)), e também há uma notação abreviada que eleva uma expressão a função com |, como em |(+ 1 $)
- Splat ou spread é expresso com
;, como em (+ ;args)
- Strings com crases podem ser abertas com qualquer número desejado de crases e fechadas com o mesmo número, e dentro delas sequências de escape como
\n não são aplicadas
- Parâmetros restantes usam
& em vez de ., sendo escritos como (defn foo [first & rest] ...)
- Janet não oferece suporte a reader macros, então a própria sintaxe é fixa, e se você sabe ler Janet, consegue ler qualquer programa Janet
Macros e estado em tempo de compilação
- Macros no Janet são código que escreve código e, em tempo de compilação, lidam ao mesmo tempo com o fluxo de execução atual que manipula valores e árvores de sintaxe abstrata e com o fluxo do código da aplicação que será executado no futuro
- Macros do Janet não são higiênicas (hygienic), e também não existe um namespace separado para funções
- Mas é possível fazer unquote de funções literais, o que permite escrever macros totalmente transparentes por referência
- Ao compilar um programa Janet, comandos de topo, instruções comuns e declarações de função são executados primeiro, e depois um snapshot do estado do programa é gravado em disco
- Esse snapshot preserva referências compartilhadas, então valores mutáveis podem continuar sendo alterados depois do reinício
- Geradores lembram qual comando deve ser executado na próxima retomada, e closures também preservam os valores fechados sobre os quais capturam
- Macros são uma forma especial de execução de código em tempo de compilação, mas essa capacidade também pode ser usada sem macros
- Em jogos, é possível pré-processar splines; em tempo de compilação, ler arquivos e colocar assets no binário final; e também realizar efeitos colaterais arbitrários
- Janet for Mortals mostra um exemplo de geração automática de bindings de banco de dados com base em arquivos de schema SQL e avalia que esse tipo de tarefa é bastante difícil na maioria das linguagens
Mais confortável que a tradição Lisp
- Janet não segue simplesmente convenções antigas do Lisp
CAR recebe o nome first, PROGN vira do, LAMBDA vira fn e SETQ vira def
nil não é uma lista vazia, mas um tipo independente, e booleanos são valores de primeira classe
- Evita a família
EQ, EQL, EQUAL, EQUALP, e listas encadeadas quase não aparecem
2 comentários
Opiniões do Hacker News
Janet tem alguns pontos fracos. Principalmente a falta de fixação de versões no gerenciamento de pacotes e de bibliotecas em geral para coisas como roteamento HTTP avançado
Ainda assim, gosto muito do fato de que dá para criar binários e scripts com o JPM e da boa portabilidade. No passado, até cheguei a portar a linguagem de programação Janet para o console de jogos Playdate como prova de conceito
Gosto de escrever código em Janet, mas é meio constrangedor que, sempre que faço isso, as pessoas achem que fui eu quem criou a linguagem
Seria legal fazer uma versão de “Janet escrevendo Janet” também
Ele faz vendoring das dependências e permite instalar facilmente bundles modernos de Janet sem o jpm
Se você estiver aberto a desenvolvimento com LLM, pode deixar o LLM escrever os wrappers e escrever a lógica real em Janet
Existe também a Fennel, uma linguagem parecida criada antes pelo mesmo desenvolvedor. Ela compila para Lua e sua implementação também é toda em Lua
Não tem biblioteca padrão própria, então faltam muitas das coisas boas do Janet, como a biblioteca de parsing, mas é boa para escrever scripts em ambientes que embutem Lua
https://fennel-lang.org/
A ligação entre o Fennel e a VM do Lua é muito frágil, e não chega nem à metade da qualidade do depurador e do REPL do Janet. É uma pena, porque o Fennel é muito mais portável e, graças ao LuaJIT, pode até atropelar o SBCL
Mas acho que a experiência de transpilation realmente atrapalha tudo. Há soluções alternativas, mas mesmo implementando
debug.setinfovocê acaba encontrando casos de borda menos agradáveis, como blocosmatchVejo muito valor em fazer um fork do LuaJIT2 para ajustar a estrutura de depuração e de erros de modo que fique mais alinhada à transparência da linguagem. Aí linguagens como Fennel pareceriam muito mais atraentes
O autor do post criou estas ferramentas em Janet, e elas já apareceram no HN antes
https://bauble.studio
https://toodle.studio
Essas duas ferramentas artísticas interessantes me deixaram bastante animado com Janet por um tempo
Sempre fico feliz em ver Janet recebendo atenção. Como um dos recursos modernos, eu destacaria o sandbox
“Desabilita conjuntos de funcionalidades para que o interpretador não consiga usar certos recursos do sistema. Não há como reativar uma funcionalidade depois de desabilitada.”
https://janet-lang.org/api/misc.html#sandbox
Quando vi “SETQ is def”, minha primeira reação foi dizer em voz alta: “quê?”. Isso porque SETQ não cria bindings, só atualiza
Lendo a documentação (https://janet-lang.org/docs/bindings.html), parece que o autor realmente errou, e lá diz que “bindings criados com def são imutáveis”. Talvez ele quisesse dizer “SETQ is set”
Quero muito gostar de Janet, porque ele parece o ponto certo entre Guile, Tcl e CL, mas tenho uma rejeição instintiva a usar vetores com colchetes para lambdas e operadores de controle de fluxo. Tenho a mesma dificuldade com Clojure, embora talvez desse para superar com esforço suficiente
E também fico curioso sobre o estado atual de LSP/SLIME. Hoje em dia isso é bem importante
Se você usa parênteses, o primeiro elemento da lista determina como o restante da lista será interpretado. Por exemplo,
(func a b c)é execução de função,(macro x y z)é expansão de macro, e([p q r] …)é um corpo de função “nu”, começando com um vetor de parâmetros e seguido por expressões executáveisColchetes são usados quando os elementos são todos do mesmo “tipo” e o primeiro elemento não é especial. Por exemplo,
(defn f [a b c] …)é uma coleção de parâmetros do mesmo tipo, em que o primeiro parâmetro não é especial, e(let [a 1 b 2] …)também é uma coleção de bindings, em que o primeiro binding não é especialA única exceção que me vem à cabeça é agrupar vários elementos de correspondência em
case, mas isso existe por conveniência. Depois que entendi essa lógica, mudei de opinião e passei a achar bonito[1 2 3], pode escrever(array 1 2 3), e em vez de(fn [x] (+ 1 x)), pode escrever(f (x) (+ 1 x))Não é obrigatório
Em scripts de sistema que passam de certo tamanho, Janet substitui sh, Python, awk etc. para mim
O tempo de inicialização da execução de scripts é muito rápido e, no meu sistema, fica em 1,4 ms no hyperfine, parecido com os 1 ms do dash. Isso é para scripts, não para binários compilados
Graças ao módulo
sh-dsl, dá para escrever comandos de shell de forma muito elegante, como($ cmda w x | cmdb y z). A capacidade de carregar imagens para depuração também ajuda bastanteComecei a usar há pouquíssimo tempo, mas já parece que vai se tornar uma das minhas linguagens favoritas, e a única outra Lisp que usei antes foi o MIT Scheme para SICP
Este post é refrescante. Tem cheiro de discussão pré-IA na internet
Há uma nova linguagem, uma nova sintaxe e discussões intensas entre pessoas que escrevem código há anos. Seria bom se alguém criasse uma comunidade online onde IA não fosse permitida
Na verdade, acho que seria mais correto dizer o relançamento anterior, e agora parece que existe outra homepage nova. A primeira pessoa que descobrir como barrar IA de forma confiável em comunidades online provavelmente tem uma boa chance de ficar muito rica
https://www.techspot.com/news/111698-digg-relaunch-fails-two...
Algum tipo de “prova de humanidade” é um problema complicado de resolver
A regra exata definida pela administração era “autoria humana significativa”, mas não se engane. Há muitas pessoas em lobsters ideologicamente contrárias a LLMs. O quanto a tecnologia foi aplicada de forma “significativa” não importa muito
Meu trabalho foi classificado como lixo só por ter sido tocado por IA, e também houve quem me chamasse de exibicionista ou fetichista quando eu disse que usava IA. Quero apenas avisar isso de antemão para quem estiver pensando em se cadastrar
Frases como “ao permitir unquote em funções literais, Janet possibilita escrever macros totalmente referencialmente transparentes” fazem parecer que o pessoal de Lisp realmente se empolga com coisas muito abstratas
Se você dissesse isso para uma pessoa comum na rua, ela provavelmente sairia correndo
#define MULTIPLY(x, y) x * yint result = MULTIPLY(2 + 3, 4); // 14Só porque alguém não sabe o significado de um termo não quer dizer que ele seja ruim. Pela formulação, acho que era isso que queriam dizer
Ter uma linguagem compartilhada para padrões e problemas que aparecem repetidamente na programação é algo bom. Zombar da terminologia com uma atitude de “esse pessoal de programação é mesmo estranho” não serve para nada e só causa efeito contrário
Estou pensando em retomar, mas fico em dúvida se vale a pena implementar recursos de nicho. Para mim também é difícil de implementar. Talvez fosse melhor pular
dynamic-unwind, quem sabe até tirarcall/cc, e focar em depurabilidade, ecossistema, desempenho e gerenciamento de pacotesEntão tento responder de forma bem vaga, algo como “trabalho com computadores”, ou digo “não é algo muito interessante” e mudo de assunto. Se eu entrar um pouco mais em detalhes, as pessoas já começam a procurar a saída
Para ser sincero, acho que comunidades da família Lisp até se beneficiaram por serem pequenas. Por exemplo, até o antiquíssimo Design Patterns já alertava para preferir composição a herança, mas programadores de orientação a objetos continuaram criando hierarquias com 15 níveis de profundidade
Quando conheci Janet pela primeira vez, estes documentos ajudaram muito
https://janetdocs.org/tutorials
https://janet.guide/ foi feito pelo autor do post
Às vezes eu me interessava por posts sobre Janet que apareciam no HN, mas Janet for Mortals, que todo mundo elogia, não pareceu nem um pouco um livro para mortais
Comparada a outras linguagens, Janet realmente está do lado mais fácil de aprender, então é surpreendente que o livro seja difícil. Não li o livro, mas tenho alguma familiaridade com a linguagem e, sinceramente, só tenho elogios
Janet parece uma espécie de Lisp 2.0, então a sintaxe também é no estilo Lisp
Opiniões no Lobste.rs
Em apenas 10 meses usando Janet, a pessoa se envolveu tanto que praticamente esqueceu quase todas as outras linguagens da família APL, e está mantendo o site de documentação da comunidade enquanto também escreve tutoriais
Em menos de 3 semanas, reescreveu todos os scripts pessoais e também passou a escrever em Janet novos softwares operacionais que cria
Em termos de implementação, Janet funciona quase como se a linguagem inteira fosse um hashmap, então é possível ver símbolos locais com
(keys (curenv))e símbolos do núcleo com(keys (getproto (curenv))); se quiser, também dá para criar algo parecido com CLOS com base em hashmap, e existe até uma implementaçãoEstá rodando cerca de 20 sites e vários serviços com o framework web Joy em uma única VPS gratuita de 512MB, e também escreveu um tutorial sobre isso
Ainda assim, a expressão “coleções imutáveis” é um pouco diferente da realidade, e a biblioteca padrão em geral retorna valores mutáveis, então hoje não há muito motivo para insistir em imutabilidade
O recurso de passar valores de tempo de compilação para o tempo de execução foi especialmente poderoso. Por exemplo, ao inserir uma
.tsvda Bíblia no binário como hashmap durante a compilação, em tempo de execução só é preciso consultar, e o resultado chegou a ser duas vezes mais rápido do que uma versão em Go usandoembedSe implementasse a mesma coisa manualmente em Go como hashmap, o código ficaria muito maior, mas em Janet foi possível até fazer um compilador de Lisp para Go em 46 linhas
A parte mais interessante que Ian Henry mencionou é que Janet preserva o estado de closures entre imagens/sessões: se você salvar o ambiente relevante no hashmap de
(curenv)e restaurá-lo em uma nova sessão REPL, o estado interno da closure continua de onde parouTambém existe a DSL musical baseada em Lisp https://lisp.trane.studio/, e vale a pena ver o artigo https://dl.acm.org/doi/abs/10.1145/3677996.3678285 e o exemplo de resultado https://x.com/greg_ash/status/1824218993118388708
Há também uma biblioteca própria que oferece uma sintaxe de consulta parecida com SQL sobre várias estruturas de dados
Ela oferece suporte a inserção e atualização em dataframes, além de salvar/carregar CSV, e inclui Datalog e miniKanren; também permite operações vetorizadas no estilo APL
Existe ainda o jnj, que usa J diretamente em Janet, e no Joy Web Framework há uma DSL de consultas a banco como
(var account (db/find-by :account :where {:login (auth-result :login)})), usada inclusive no código real de autenticação de um siteQuando se fala em “coleções imutáveis”, o mais natural é pensar em estruturas de dados persistentes, e isso, embora útil, não é um recurso básico de Janet
O que agrada de fato é a simetria entre tipos por valor e tipos por referência, e embora no fim do texto apareça “immutable composite values”, se eu não tivesse escrito isso talvez eu mesmo não entendesse de imediato o que queria dizer
Janet é uma linguagem nova e embutível, então eu gostaria de experimentá-la como script interno em projetos como engines de jogo
Parece combinar bem com casos em que é preciso hot reload para iteração rápida sem trocar DLLs; Lua também é ótima, mas Janet parece mais expressiva em alguns aspectos
Janet é realmente muito legal como linguagem, e algum dia eu gostaria de usá-la como linguagem de script em um projeto com Zig. Fico feliz em ver mais gente falando de Janet
Parece interessante, mas eu já estou me acostumando com scripting em Clojure usando babashka, então a sensação é parecida. Fora a capacidade de embutir, fico curioso sobre qual seria a grande vantagem que posso estar deixando passar
Partes como “desestruturação de array com argumento rest pode causar cópias potencialmente caras” me desagradam porque fazem tudo parecer menos funcional no geral
É daquelas funcionalidades cuja falta se sente toda vez que se precisa fazer parsing de texto em outra linguagem
Janet está mais para um Lua com melhor suporte a programação funcional do que para um Clojure pequeno e embutível