Elmord's Magic Valley

Software, lingüística e rock'n'roll. Às vezes em Português, sometimes in English.

Blueprints for a shell, parte 0: Visão geral

2015-03-10 22:32 -0300. Tags: comp, prog, shell, pldesign, lash, em-portugues

Sim, meus caros, o mui lendário e prometido shell que eu estou há anos dizendo que quero escrever está mais perto do que nunca de talvez ser escrito. Isso se deve a uma decisão de vida curiosa que me deixou com mais tempo para projetos pessoais, pelo menos por enquanto.

A questão é: tem um trilhão de decisões de design que eu preciso tomar e que eu gostaria de pensar bem sobre e discutir antes de começar a implementar. Assim, me pareceu uma boa escrever sobre elas aqui para me ajudar a organizar as idéias e coletar comentários, sugestões e opiniões. A idéia original era escrever um único post com tudo, mas eu comecei a fazer isso ontem e me dei conta de que ele ia acabar ficando gigante. Então, o plano agora é escrever uma série de posts. Neste aqui, apresentarei as idéias básicas do novo shell, e nos próximos pretendo entrar nos detalhes de features mais específicas, tais como tipos de dados, quoting, closures, estruturas de controle, módulos, escopo de variáveis e afins.

Por que um novo shell?

Eu já escrevi um post (gigante) sobre o assunto antes, mas basicamente: o shell é uma péssima linguagem de programação. Embora o bash tenha adquirido inúmeras features ao longo dos anos, coisas que se esperam de qualquer linguagem de programação que se leve a sério, tais como dados estruturados de primeira classe e a possibilidade de retornar valores de funções sem criar um subprocesso, não existem até hoje. Acho que existe um círculo vicioso na evolução dos shells: shells não são vistos como linguagens de programação "de verdade" por seus usuários por terem programabilidade pobre, e os desenvolvedores de shells não melhoram a programabilidade do shell porque não há demanda dos usuários. A premissa do novo projeto é romper com essa idéia e tornar o shell uma linguagem "decente" como Perl, Python ou Ruby, sem entretanto perder as características que tornam um shell conveniente, i.e., a facilidade de chamar e combinar programas de linha de comando e de utilizá-lo como uma interface interativa para o sistema operacional.

Objetivos gerais

Eis uma lista das idéias básicas que hão de guiar o desenvolvimento desse novo shell.

A sintaxe de uso interativo freqüente deverá permanecer largamente igual à do (ba)sh. Coisas como redirecionamentos simples (>, >>, <), pipes, globbing (*.txt, /dev/tty[1-8]), tilde expansion (cd ~/Desktop), etc., manterão a mesma sintaxe. A sintaxe dessas coisas é tradicional demais (e familiar até a usuários de ambientes não-Unix), então me parece melhor mantê-la igual, mesmo que isso limite as escolhas sintáticas para outras funcionalidades do shell.

Dito isto, compatibilidade com (ba)sh não é um objetivo do shell. A manutenção da sintaxe das funções mais comuns é mais uma questão de compatibilidade com os usuários de shell do que com os shells propriamente.

Uma das features mais importantes do shell novo é o suporte a dados estruturados de primeira classe. Isto é, arrays e dicionários podem ser armazenados em variáveis, dentro de outros arrays e dicionários, passados como argumento para funções, retornados por funções, etc. Isso implica a adição de um mecanismo para retorno de valores complexos por funções, bem como uma sintaxe para chamar uma função e capturar seu valor de retorno, sem criar um subshell para isso (diferente do $(...) do (ba)sh).

Outra feature importante é o suporte a closures, ou blocos de código de primeira classe. Isso permite a substituição de diversas estruturas de controle que têm sintaxe especial no (ba)sh (if, for, while, etc.) por comandos simples que recebem blocos de código como argumento, e também permite que novas estruturas de controle sejam definidas pelo usuário.

O suporte a closures e a funções com valores de retorno complexos nos possibilita fazer uma grande limpeza na sintaxe do shell, substituindo certos elementos de sintaxe questionável (e.g., ${var,,*}) por equivalentes mais legíveis (e.g., $[lowercase $var]). A idéia é inicialmente ter o mínimo de sintaxe especial. Porém, sintaxe minimalista não é necessariamente um princípio sagrado, e se for observado que algumas operações são freqüentes o suficiente para justificar uma sintaxe especial, tal sintaxe pode vir a ser acrescentada ao shell.

O shell deve facilitar a escrita de scripts robustos. Em (ba)sh é muito fácil escrever um script que aparentemente funciona corretamente, mas falha diante de nomes de arquivo com espaços ou quebras de linha, ou comandos que usam * ou ? com seu sentido literal e funcionam 99% do tempo, mas falham misteriosamente ocasionalmente, porque coisas como *~ são mantidas intactas pelo (ba)sh quando não há nenhum arquivo que case com o padrão, o que faz com que o comando funcione ou não dependendo do conteúdo do diretório atual, ou porque o (ba)sh expande caracteres epeciais em situações inesperadas. O shell deve ter um comportamento consistente, fácil de "reason about", sem dependências mágicas de contexto e do ambiente do usuário.

Uma preocupação secundária mas importante é que o shell deve ser razoavelmente rápido. Não necessariamente rápido como uma chita, mas idealmente com uma performance equiparável à de Python ou Ruby. (Isso não precisa ser uma preocupação inicialmente, mas é bom mantê-la em mente durante o design da linguagem.) O bash é absurdamente lento, e shell scripts em geral tendem a ser lentos por terem que usar comandos externos para diversas coisas que em outras linguagens seriam builtins ou bibliotecas. O que nos leva ao próximo item...

O shell deve ter suporte a bibliotecas, módulos ou algo do tipo para reuso de código. Idealmente, também deve ser possível escrever bibliotecas/módulos compilados que possam ser utilizados por scripts. Isso permite a adição de features sem engordar o shell. Deve ser fácil distribuir e instalar bibliotecas para o shell. Idealmente, também deve ser fácil determinar e instalar (semi-)automaticamente as bibliotecas das quais um script depende. Deve ser possível isolar namespaces para evitar conflitos de nomes.

Finalmente, features interativas, como edição de comandos e histórico, não devem ser parte do core do shell. Com suporte a bibliotecas/módulos, não há por que colocar essas funcionalidades no binário principal e carregá-las ao rodar scripts, que não precisam delas.

Remarks on syntax

O fato de que o shell deve ser conveninete de usar interativamente, e de que desejamos manter um mínimo de "compatibilidade de usuário" com a sintaxe tradicional do sh, impõe certas restrições nas escolhas sintáticas do shell. Por exemplo, em uso interativo, strings literais são mais freqüentes do que variáveis, então faz sentido que strings não exijam aspas e variáveis sejam introduzidas por um símbolo especial ($). < e > possuem significados convencionais, então embora às vezes seja muito tentador utilizá-los como delimitadores para alguma outra coisa, o melhor é deixá-los em paz.

Essas restrições fazem com que seja difícil escolher uma sintaxe para certas features que seja "ergonômica" para programar e ao mesmo tempo não interfira e se encaixe direito com o resto do shell. Até hoje eu não encontrei uma sintaxe para capturar o resultado de uma função que me agrade totalmente, por exemplo.

Dito isso, embora eu seja da opinião de que "syntax matters", eu cheguei à conclusão de que, pelo menos inicialmente, considerações sintáticas não são tão importantes assim, já que em geral a sintaxe pode ser alterada mais adiante sem muito impacto no resto do projeto (pelo menos enquanto ele não for oficialmente released e não tivermos que lidar com esse negócio de "usuários"). Assim, vou aceitar por ora uma certa dose de bizarrice sintática quando não houver uma escolha obviamente melhor, deixando aberta a possibilidade de mudanças futuras.

Um outro fator ao qual eu pretendo dar um peso importante ao definir a sintaxe é o que podemos chamar de dificuldade de interpretação incorreta. Por exemplo, eu costumava não ir muito com a cara do uso de my em Perl para declarar variáveis locais, mas ele tem o mérito de deixar claro (para mim, pelo menos) que trata-se de uma declaração de variável (e não uma atribuição a variável já existente), e que o escopo dela é o bloco em que se encontra (e não a função ou o módulo ou whatever). let também é relativamente claro, mas let é um comando que faz algo diferente em bash (avaliação de expressões aritméticas e atribuição (não declaração)), então talvez seja melhor evitar essa palavra (mas ainda estou meio em dúvida).

Um contra-exemplo é a sintaxe para declaração de variáveis de ambiente temporárias: em um comando como

LC_ALL=C find /home | grep '[^A-Za-z0-9]'

o LC_ALL=C vale só para o find, ou para o grep também? (Resposta: só para o find.) Em um comando como

foo=42 echo $foo

$foo está no escopo da definição ou não? (Resposta: não.) No geral, acho preferível escolher uma sintaxe que não deixe dúvida de qual é a interpretação correta. Similarmente, se alguma distinção é importante, pode ser melhor obrigar o usuário a especificá-la ao invés de usar um default que freqüentemente pode não ser o que o usuário quer, ou que pode induzir ao erro alguém lendo o código escrito por outra pessoa. Por exemplo, eu não pretendo ter uma função len para strings no shell, mas sim funções como bytelen (número de bytes), charlen (número de codepoints Unicode) e charwidth (largura do texto na tela), exigindo que o programador seja específico quanto a o que quer dizer com "comprimento" da string. (Esses nomes ainda são meio questionáveis, pois o encoding das strings (que supostamente é UTF-8) fica implícito, mas ainda não pensei com calma sobre o assunto.)

A teaser

Embora por enquanto nada esteja muito bem definido, para tornar as coisas um pouco mais concretas, eis um exemplo da cara que eu imagino que a tal linguagem vai ter:

# Função que retorna um dicionário contendo a quantidade de usuários
# que usam cada shell.

def count_shell_users {
    my counts = %()
    each_line </etc/passwd {|line|
        my (user pass uid gid name home shell) = $[split $line ":"]
        counts{$shell} = $(( $[or $counts{$shell} 0] + 1 ))
        # (A sintaxe da linha acima provavelmente não é definitiva.)
    }
    reply $counts
}

# Função que retorna todos os elementos de uma lista que satisfaçam um predicado.

def filter {|list predicate|
    my result = ()
    each $list {|item|
        if {$predicate $item} {
            push $result $item
        }
    }
    reply $result
}

# Exemplo de uso.
my dirs = $[filter (/etc/*) {|x| isdir $x}]

Por hoje é só

Por hoje ficamos por aqui. Nos próximos episódios trataremos de tópicos mais específicos. Perguntas, sugestões, opiniões, comentários, tanto sobre os tópicos abordados quanto sobre outras coisas que você gostaria de ver (ou não) num shell, são muito bem-vindos.

(By the way, não havendo conflito com nenhum projeto ativo, o shell a princípio deverá se chamar lash (lambda shell).)

Comentários / Comments (8)

Marcus Aurelius, 2015-03-11 12:37:06 -0300 #

Legal, gostei de todos os princípios do design.

Perguntas:
1) Qual o nível de maturidade que se pretende alcançar com o projeto? (a)
Dominar o mundo; (b) ir fazendo e ver no que dá; (c) ir fazendo, mas já sabendo que vai aparecer algo mais interessante ("oh, shiny!") para fazer depois. Meus projetos solo geralmente caem na categoria c.

2) Tem um monte de shells por aí que eu nem conheço direito. Nenhum deles tentou atingir o mesmo objetivo? (isto é, sintaxe e semântica robustas sem perder aquelas coisas comuns de um shell) Também fiquei pensando em comparações com TCL e PowerShell.


Marcus Aurelius, 2015-03-11 12:39:12 -0300 #

OK, ok, o post The shell is completely wrong responde tudo ou uma boa parte da pergunta 2...


Marcus Aurelius, 2015-03-11 12:40:49 -0300 #

Que vontade de poder editar comentários anteriores
("uma tudo", argh)


Vítor De Araújo, 2015-03-11 14:20:19 -0300 #

O nível de maturidade sonhado é "bom o suficiente para eu nunca mais ter que usar o bash" (i.e., alternativa (a)), mas no momento prefiro não prometer nada e ver aonde a coisa chega (b). Haverá coisas mais interessantes para fazer (c) se/quando eu tentar lidar com o problema de passar dados estruturados entre processos, o que também está relacionado com umas outras idéias mais mirabolantes sobre as quais eu prefiro não entrar em detalhes no momento porque é lunatismo demais, e por ora eu quero tentar (uma vez na vida) projetar algo factível. :P So, todas as alternativas acima. :P

Quanto aos outros shells, o PowerShell parece ter uma porção de idéias interessantes que podem servir de inspiração, mas eu nunca o usei e teria que estudá-lo melhor. O shell do Inferno também tem features legais, como uma sintaxe limpa e módulos, mas não tem dados estruturados de primeira classe. Nenhum dos dois roda num GNU/Linux nativamente, que eu saiba. :P

O Tcl eu até já comentei antes que poderia dar um bom shell, mas teria que ter umas adaptações sintáticas para suportar globbing, pipes, redirects, etc. E eu não consigo me convencer de que armazenar tudo como string é uma boa idéia. :P

Quanto a edição de comentários: um dia, um dia! :P (Mas eu vou modificar o comentário, for great justice.)


Marcus Aurelius, 2015-03-11 15:58:51 -0300 #

Dá pra fazer uns slogans com essas informações:

A robust shell that you can reason about.
TCL with globbing, pipes, redirects and data structures.
A shell that's not stringly typed.
Runs on GNU/Linux: the features of PowerShell and Inferno's shell without going to hell.


Vítor De Araújo, 2015-03-11 17:13:58 -0300 #

Hahahah, vou te contratar pra fazer a publicidade do projeto. :P


Marcus Aurelius, 2015-03-11 21:49:32 -0300 #

Ah, a próxima grande pergunta: implementado em qual linguagem? :-)


Vítor De Araújo, 2015-03-11 23:20:59 -0300 #

Acabei de responder (vagamente) no outro post, mas: pelo menos inicialmente, Chicken Scheme. Motivos incluem uma interface decente com POSIX, memory-safety, facilidade de integrar com coisas em C se necessário, habilidade de gerar um executável razoavelmente pequeno, e o fato de que não é C. :P

Eu deixo em aberto a possibilidade de reimplementar em outra linguagem no futuro, most likely Rust depois que ele sair de alpha.


Deixe um comentário / Leave a comment

Main menu

Posts recentes

Comentários recentes

Tags

em-portugues (213) comp (138) prog (68) in-english (51) life (47) unix (35) pldesign (35) lang (32) random (28) about (27) mind (25) lisp (23) mundane (22) fenius (20) web (18) ramble (17) img (13) rant (12) hel (12) privacy (10) scheme (10) freedom (8) bash (7) copyright (7) music (7) academia (7) lash (7) esperanto (7) home (6) mestrado (6) shell (6) conlang (5) emacs (5) misc (5) latex (4) editor (4) book (4) php (4) worldly (4) politics (4) android (4) etymology (4) wrong (3) security (3) tour-de-scheme (3) kbd (3) c (3) film (3) network (3) cook (2) poem (2) physics (2) wm (2) treta (2) philosophy (2) comic (2) lows (2) llvm (2) perl (1) en-esperanto (1) audio (1) german (1) kindle (1) old-chinese (1) pointless (1) translation (1)

Elsewhere

Quod vide


Copyright © 2010-2020 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.