1 pontos por GN⁺ 2 시간 전 | 2 comentários | Compartilhar no WhatsApp
  • 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

 
GN⁺ 2 시간 전
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

    • Há um post divertido da Julia Evans visualizando o Gunzip em Julia: https://jvns.ca/blog/2013/10/24/day-16-gzip-plus-poetry-equa...
      Seria legal fazer uma versão de “Janet escrevendo Janet” também
    • Fico curioso se você já usou o jeep: https://github.com/pyrmont/jeep/
      Ele faz vendoring das dependências e permite instalar facilmente bundles modernos de Janet sem o jpm
    • Se precisar do lado do servidor, como o veqq mencionou, existe o joy, que implementa seu próprio servidor HTTP leve. Se precisar de cliente, empacotar o libcurl ou outro cliente HTTP com a Janet C API é bem simples
      Se você estiver aberto a desenvolvimento com LLM, pode deixar o LLM escrever os wrappers e escrever a lógica real em Janet
    • Fico curioso sobre o que exatamente significa roteamento HTTP avançado. Faço todo o meu trabalho web com https://github.com/joy-framework/joy, então, se estiver faltando alguma funcionalidade, provavelmente dá para colocar em uma semana
  • 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/

    • Fennel é realmente ótima e também é uma boa porta de entrada para a família Clojure. Minha maior reclamação é que a depuração é um típico campo minado de transpilers
      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.setinfo você acaba encontrando casos de borda menos agradáveis, como blocos match
      Vejo 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

    • É um recurso realmente muito legal, mas fico curioso sobre em que situações o programador médio precisaria desse tipo de sandboxing
  • 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

    • O uso de colchetes na sintaxe do Clojure é muito consistente e bastante lógico
      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áveis
      Colchetes 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 é especial
      A ú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
    • Você pode simplesmente não usar colchetes nem chaves. Em vez de [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
    • Se você entender como a desestruturação funciona em Clojure, o papel dos colchetes fica claro
  • 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 bastante
    Comecei 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

    • Para mim, babashka substituiu sh, Python, awk etc.
  • 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

    • Se você não acompanhou o rumo recente, o relançamento mais recente do digg.com fracassou porque não conseguiu lidar com a avalanche de bots
      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...
    • Penso com frequência em como seria possível criar uma comunidade online onde IA não fosse permitida. Especialmente sem destruir o anonimato online, isso parece difícil
      Algum tipo de “prova de humanidade” é um problema complicado de resolver
    • O impressionante da IA é que, mesmo que você não seja um entusiasta de IA, ela pode ser inserida em conversas que não têm nada a ver com IA. Os opositores fazem isso por você
    • Esse lugar provavelmente seria o lobsters. Depois de uma discussão com mais de 300 comentários, provavelmente uma das maiores da história do site, textos gerados por IA foram proibidos
      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
    • Ironicamente, este comentário, que é o de maior destaque, agora virou um comentário sobre IA
  • 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

    • Um exemplo de macro em C com literal e sem transparência referencial é este
      #define MULTIPLY(x, y) x * y
      int result = MULTIPLY(2 + 3, 4); // 14
      Só 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
    • Fico curioso se você já tentou explicar para uma pessoa comum na rua o que é uma linguagem de programação orientada a objetos e quais são as suas vantagens
    • Cerca de um ano atrás comecei a escrever um interpretador de Scheme e avancei bastante. Parei alguns meses atrás quando consegui um emprego novo
      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é tirar call/cc, e focar em depurabilidade, ecossistema, desempenho e gerenciamento de pacotes
    • Sempre que me perguntam “o que você faz?”, normalmente recebo mais ou menos esse tipo de reação
      Entã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
    • O programador médio provavelmente também é assim
      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

    • Também há material introdutório mais suave: https://janetdocs.org/tutorials
    • Surpreendente. Essa linguagem é muito intuitiva, simples e tem pouquíssimas regras para memorizar. É uma Lisp, mas sua superfície é bem pequena
      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
    • Pessoalmente, eu travo porque a sintaxe de macros aparece cedo demais, mas depois disso há muito conteúdo realmente valioso
    • Tive essa sensação com Haskell. Haskell é difícil demais para mim, mas gosto da sintaxe em si
      Janet parece uma espécie de Lisp 2.0, então a sintaxe também é no estilo Lisp
 
GN⁺ 2 시간 전
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ção
    Está 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 .tsv da 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 usando embed
    Se 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 parou

    • Um exemplo bem legal feito em Janet é https://bauble.studio, e há também um texto sobre o processo de criação em https://ianthehenry.com/posts/bauble/building-bauble/. O uso de WASM chama atenção
      També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 site
    • Parece que chamar isso de “coleções imutáveis” no texto original foi uma formulação imprecisa. O que se queria dizer estava mais próximo de tipos de valor compostos e tipos por referência
      Quando 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

    • Acho que um dos recursos matadores de Janet é o suporte embutido a PEG. Foi realmente muito conveniente ao criar uma DSL de textbox para um projeto paralelo de desenvolvimento de jogos
      É daquelas funcionalidades cuja falta se sente toda vez que se precisa fazer parsing de texto em outra linguagem
    • Esse tipo de desestruturação não é um padrão de uso tão frequente, e arrays/tuplas de Janet não são listas ligadas
      Janet está mais para um Lua com melhor suporte a programação funcional do que para um Clojure pequeno e embutível