Red vs Crystal

Vi hoje no Twitter um post do TaQ “An Introduction to Crystal: Fast as C, Slick as Ruby” sobre Crystal. Pelo título fui dar uma olhadinha e achei interessante e, pelos objetivos, achei semelhante a Red (o título pelo menos).

Como conheço mais Red do que Crystal, tentar comparar ambos está mais para presunção do que análise isenta. Então, desculpe qualquer erro nas comparações abaixo, mas resolvi fazer um apanhado de semelhanças e diferenças.

Semelhanças e diferenças (Crystal vs Red)

  • Estágio inicial: Ambas são consideradas beta ou alfa (apesar de ja ser possível fazer muita coisa em Red; acredito que em Crystal também). Red está na versão 0.6.1 e Crystal na versão 0.18.0. Pela abrangência e complexidade de Red, é bem provável que Crystal alcance a versão 1.0 antes de Red.
  • Plataformas: Pelo que vi, Crystal está disponível para Linux e Mac OSX. Atualmente, Red está disponível para Linux, Mac OS X e Windows. Em breve para Android.
  • Instalação: Red não precisa de instalação. É só baixar o executável para a plataforma desejada e executar um script/compilar/usar o REPL.
  • Compatíveis mas não iguais: Red possui a sintaxe parecida com Rebol e Crystal parecida com Ruby. Em algumas situações, Red executa programas feitos em Rebol e Crystal em Ruby. Quem deseja migrar deve estar ciente de que precisará efetuar alterações no código e nada garante que o código funcione sem algum hack.
  • Compilar para código nativo: Com Red, você pode baixar e rodar a versão para Windows e gerar executáveis para qualquer plataforma disponível sem se preocupar com mais nada a não ser a chave -t <plataforma>. É bom deixar claro que Red é composto por duas partes. Red que apresenta grande compatibilidade com Rebol e é de alto nível e Red/System que seria um substituto para o C, compartilhando a mesma sintaxe de Red mas com menos funções. O resultado é que compilando o exemplo da sequência de Fibonacci em Crystal (que está incorreto) o executável ficou com 294K ao passo que em Red/System ficou com 6,4K.  (Crystal foi 0,05s mais rápido que Red). Outro detalhe é que Red pode gerar bibliotecas (.dll, .so, .dylib) que podem ser utilizadas por outras linguagens.
  • Tipagem: Em Crystal, as vezes não é necessário informar o tipo do dado e em outras é (não sei se pode confundir ou não). Em Red não é necessário especificar o tipo de dado mas, se o usuário desejar, poderá especificar para os parâmetros de uma função (pode ser mais de um tipo como integer, string) e assim será gerado erro se um tipo de dado diferente for enviado em vez do erro aparecer apena em tempo de execução. Red/System possui tipagem estática (como pode haver uma interface entre Red e Red/System, existem algumas facilidade para trabalhar com tipos diversos de dados)
  • Interface com bibliotecas: Crystal oferece facilidades para o acesso a bibliotecas escritas em C (não sei se outras linguagens) sem a necessidade de sair da linguagem (escrever um programa em C, por exemplo). Red/System também (e Red por intermédio de Red/System). Como o objetivo de Red/System é ser um “substituto” de C para quem programa em Red, também permite o acesso a syscall (diretamente ao Kernel).
  • Fontes: Ambas linguagens disponibilizam os fontes. Para compilar Crystal você precisa ter Crystal (existem várias linguagens com esta recursividade). Para compilar Red, você precisa ter Rebol (versão comercial). Mas existe a possibilidade de você usar a versão grátis para criar executáveis com os fontes de Red. Ou você pode usar a versão compilada para Windows, Linux e Mac que os desenvolvedores utilizam. Futuramente Red será compilado com Red e a dependência deixará de existir.
  • Espaço armazenamento: Pelo repositório do Arch, diz que eu tenho que baixar 7,5M e, depois de instalado, irá ocupar 57,9M. Para Red eu tenho que baixar 1M do executável e … só. Na realidade, ele cria um arquivo de 500K que é o console (REPL) e uma biblioteca de 10k que são necessários para a execução (considere que em 1M ele já compila para três plataformas diferentes). Não é um item muito relevante mas considerando IoT, pode ser levado em conta (ou se as operadoras limitarem o consumo de dados).

Público alvo

 Bem, qualquer linguagem serve para qualquer um. Vou apenas escrever um pouco para deixar o artigo maior

Crystal

  • Como o ideal não é a compatibilidade com Ruby que tem Rails como carro chefe, certamente não irá abocanhar uma grande parcela de desenvolvedores do Ruby se não for compatível (no artigo diz que não roda Rails).
  • Para outros usuários de Ruby, será necessário analisar com calma o custo/benefício da mudança.
  • Para quem está testando novas(?) linguagens, é uma nova opção para incluir na lista (Go, Rust, Elixir, Red, Crystal, Eve, etc.)

Red Red/System

  • Como Carl Sassenrath encerrou o desenvolvimento de Rebol 2 e disponibilizou os fontes (fora as partes que possuíam copyright) mas largou de mão, muitos ficaram órfãos. O desenvolvimento da versão 3 pela Atronix também data de 2/3 anos atrás. Red pode ser adotado por quem está sem alternativas (e por outros também).
  • Para quem possui um grande leque de aplicações que vão de drives para o SO até aplicativos para rodar no Windows+Mac+Linux+Android (outras opções no futuro como iOS) pode ser uma boa opção.
  • Para quem está de olho na IoT e não quer muita complicação também poderá ser uma opção interessante.
  • Para quem está testando novas linguagens, blá, blá, blá.
Cuidado. O texto abaixo é altamente tendencioso.
O meu interesse por Red é pela descomplicação que ele parece ter (facilidade de passear por diversos SOs sem precisar nem instalar e com 1M), escrevo um programa e gero executável para diversos ambientes, pelo que pode fazer de baixo nível (falar com o Kernel que não é o meu caso) até alto nível (criar DSL ou dialetos como são chamadas em Red), usar GUI nativa dos diversos SOs (atualmente só Windows mas para as próximas versões Android, Mac e Linux/GTK3) e previsões futuras como módulos acopláveis (usar a GPU, por exemplo), meta-DSL (facilidade para a criação de DSLs). Resumindo, parece que os desenvolvedores miraram nas estrelas para alcançar a lua.

Um exemplo que acho legal em Red é o mostrado abaixo. Em poucas linhas é possível criar uma janela, editar um código e ver em tempo real o resultado. Se você tiver curiosidade, basta baixar Red para Windows (é a versão 0.6.2 que está em desenvolvimento e possui o nome de red-latest.exe), baixar o código fonte do relógio que possui 51 linhas contendo o código para criar o ambiente, para criar o relógio e mais 16 linhas que falam sobre o programa. Funciona no wine, bastando tiraro comando transparent da primeira linha. Interessante ler outras informações no site.

blue-clock2

Clock livecoding demo

Vai demorar um bom tempo para Red chegar na versão 1.0. Alguns acham que a versão 0.7 já poderia ser considerada a 1.0 pois possui suporte a parte de I/O. Como não venho de Rebol, olho para o que está considerado para I/O e penso: “Não precisa de tudo aquilo para I/O. Já é possível ler e escrever arquivos, diretórios, posso ler até páginas pelo protocolo http[s]”. Mas se eles dizem …

Anúncios

Red – Parser

Introdução

É possível dizer que o objetivo do Parser no Red é o mesmo das Expressões Regulares (ER) em outras linguagens de programação. As diferenças básicas são:

  • Criado como uma dialeto (DSL) em Red.
  • Mais legível que uma ER (depende?).
  • Por ser criado em Red, possui uma grande integração com a linguagem.

O objetivo é, dada uma entrada qualquer (em Red é uma série como string, lista, etc.) e, baseando-se em determinadas regras, iremos verificar as ocorrências que casam na entrada para tomarmos diversas decisões. Em Red, podemos apenas verificar o casamento das regras retornando falso ou verdadeiro, coletar as partes onde a regra casa ou alterar os trechos onde as regras casam, etc..

Vejamos um caso prático e simples:

Desejamos verificar se o formato de um CPF é válido. Iremos aceitar valores no formato de três séries de três números, separados por ponto, um separador para o controle que poderá ser hífen ou barra e uma sequência de dois número para o controle. Ficaria algo como 999.999.999[-/]99. Uma das opções para o Parse em Red seria:

digitos: charset "0123456789"
centena: [3 digitos]
dezena: [2 digitos]
separador: charset "/-"
cpf: [centena dot centena dot centena separador dezena]

Diferente das ERs, Red não vem com a definição de dados numéricos (\d) para o Parser. Mas, como pode ser visto, é simples de definir. Também definimos centena como uma sequência de três dígitos e dezena como uma sequência de dois dígitos para facilitar a leitura da regra. Definimos os dois caracteres para o separador. O charset especifica que qualquer um dos caracteres irá casar com a regra. Poderíamos definir como [“/” | “-“], isto é, barra ou hífen. Se definíssemos apenas como “/-“, para casar, os dois caracteres deveriam estar presentes na ordem indicada. Alguns caracteres como dot, newline, cr, etc. já estão definidos na linguagem. Optei por usar o dot em vez de “.” para facilitar a leitura da regra. Finalmente criei a regra cpf (que é composta por várias outras regras) para validar se uma entrada é um CPF válido ou não (não estou calculando o controle, apenas o formato da entrada).

Se eu estiver no REPL do Red, uma sessão típica poderia ser:

red>> parse "100.010.001-00" cpf
== true

red>> parse "000.000.000/00" cpf
== true

red>> parse "999.111.555/77" cpf
== true

red>> parse "192.168.45" cpf
== false

red>> parse "192.168.45-77" cpf
== false

Outra possibilidade para a utilização da regra CPF seria para extrair todos os CPFs de um texto. Supondo o seguinte texto:

NOME: João
IP: 192.45.77.44
CPF: 111.111.111/11
NOME: Antônio
IP: 194.58.58.9
CPF: 123.456.789-00
NOME: Empresa Ltda.
IP: 201.195.45.44
CGC: 44.555.666/0001-11

No REPL e supondo-se que o texto estivesse sendo referenciado por s, teríamos algo como:

red>> parse s [collect some [keep cpf | skip]]
== ["111.111.111/11" "123.456.789-00"]

Aqui o collect irá colocar os CPFs da entrada em uma lista e o keep informa o que deverá ser armazenado na lista. No caso, keep cpf ou salta para a próxima posição na entrada.

Entre as outras utilizações para a regra, a possibilidade de padronizar o formato do CPF tipo trocar a barra pelo hífen.

Como falei antes “integrado com a linguagem Red”, é possível passar um código para a regra e ele será interpretado quando a regra é satisfeita. Por exemplo (considerando o texto acima):

parse s [some [copy v cpf (print ["Encontrei:" v]) | skip ]
Encontrei: 111.111.111/11
Encontrei: 123.456.789-00
== true

Quando a regra casar com a entrada, v conterá uma referência ao valor encontrado e o código que estiver entre parênteses será executado.

Sei que você não vai dizer, mas caso diga algo como: “Ah mas com ER eu escrevo menos. Seria algo como \d{3}\.\d{3}\.\d{3}-\d{2}”

Se o problema fosse só quantos caracteres você digita, certamente você iria programar em J ou K. Se você não programa nessas linguagens, você não está sendo 100% sincero. O ideal seria a gente pensar, pressionar enter e …. o programa se materializar no disco. Bem, também é possível diminuir a verbosidade no Red. Poderia ter algo como:

D: charset "0123456789"</pre>
<pre>cpf: [3 D dot 3 D dot 3 D "-" 2 D]

Não ficou tão ruim né? Pessoalmente, até acho um pouco mais legível que a ER para um leigo.

Por curiosidade, se você quiser mostrar o título de uma página html, o código ficaria assim:

page: read http://www.red-lang.org/
parse page [thru &lt;title&gt; copy titulo to &lt;/title&gt;]
print titulo

e será apresentado “Red Programming Language”.

Recursividade

As regras podem ser recursivas. Se você deseja validar se uma entrada é uma expressão matemática válida, você pode aplicar algo como o código abaixo. Pense em algo como BNF.

expr:    [term ["+" | "-"] expr | term]
term:    [factor ["*" | "/"] term | factor]
factor:  [primary "**" factor | primary]
primary: [some digit | "(" expr ")"]
digit:   charset "0123456789"

Para as regras acima você usou, basicamente, apenas o que já foi mostrado neste artigo. O único acréscimo foi de some que significa 1 ou mais ocorrências, isto é, no mínimo um dígito. Tem o any que significa zero ou mais. Se você conhece ER irá associar imediatamente os dois meta-caracteres para as duas funções.

Então parse “1+2*(3-2)/4” expr irá retornar true (é uma expressão válida para a regra) ao passo que parse “1-(3/)+2” expr irá retornar false (a expressão é inválida conforme a regra).

Então é isso. Ficar por aqui para não escrever um livro (a única forma de abranger detalhadamente tudo).

 

Red -convertendo imagens para tons de cinza

Resolvi fazer um programa em Red (lembrando que ainda está em desenvolvimento e na versão 0.6) para converter uma imagem colorida em preto e branco (tonalidades de cinza). Basta que as cores R, G e B possuam o mesmo valor para que a imagem seja mostrada com tonalidades de cinza. Basicamente temos seis formas de conversão, cada qual apresentando resultados característicos.

Claridade

O nível do cinza será calculado como
Claridade = (max(R, G, B) + min(R, G, B)) / 2

Luminosidade

O nível do cinza será calculado como
Luminosidade = 0.21 × R + 0.72 × G + 0.07 × B

Brilho Médio

O nível do cinza será calculado como
Brilho médio = (R + G + B) / 3

Canais

Escolhemos um dos canais (R, G, B) e replicamos e seu valor nos outros dois canais. Esta técnica simula uma foto feita com um filme preto e branco com a utilização de um filtro com uma das cores dos canais. O resultado é que as cores complementares serão bloqueadas deixando passar apenas a cor do filtro. Utilizando um filtro vermelho em uma foto onde aparece o céu azul e com nuvens brancas, o céu ficará bem mais escuro entregando uma maior dramaticidade para a foto.

O efeito obtido por cada uma das técnicas pode ser visto nas imagens abaixo.
Como a parte gráfica para Mac e Linux ainda estão em desenvolvimento (a do Windows também mas está mais próxima do resultado final), o programa só roda no Windows. Futuramente deverá rodar no Mac, Linux, etc..

Casualmente o programa ficou com 100 linhas como pode ser visto abaixo.

Resultado das diferentes conversões para visualizar a diferença entre elas:

O programa ainda sofrerá alterações mas, por enquanto, era isto.

Cadastro de Clientes em COBOL – CRUD

Apesar de eu achar que a versão estável do gnu COBOL necessite diversas melhorias que veremos no decorrer do artigo, não impede a sua utilização para aplicações diversas. Resolvi fazer um programinha CRUD (Create, Read, Update, Delete) para cadastrar clientes. A ideia veio depois de ligar para uma empresa que entrega água e eles pedirem o endereço mesmo já tendo comprada deles. É só a empresa ter um cadastro, pedir o telefone, informar ao programa e confirmar o endereço. É um programa simples e foi feito mais para mostrar algumas características da linguagem do que para mostrar boas técnicas de programação.

Abaixo, podemos ver o programa sendo executado, mostrando as opções de inclusão, consulta, alteração e exclusão de registros de clientes.

O fonte do programa encontra-se no github. Para baixar, você pode usar o botão Download zip ou no botão raw e salvar como clientes.cob. Para acompanhar as explicações abaixo, é interessante que você abra o link acima em outra janela ou aba. Não vou colocar o número de linha nas explicações pois existe a possibilidade de alterações e teria que adequar o artigo. Também é interessante pois obriga você a ler o programa até achar a entrada especificada.

FILE-CONTROL

Temos as definições de como será feito o acesso ao arquivo. Podemos ter diversas entradas select, uma para cada arquivo. O arquivo pode estar em disco (nosso caso), fita etc.. Será indexado pela chave fs-key e o status fs-stat nos informará o resultado das operações (chave existe, não existe, erro na abertura, etc.). Podemos incluir chaves alternativas e as mesmas poderão ser duplicadas, o que não acontece com a chave principal.

FILE SECTION

Cada arquivo definido em file-control deverá ter uma entrada FD na file section. Aqui informamos o nome do arquivo (poderia estar no select no lugar de DISK) e a estrutura do registro. Inclui um nível em FS-KEY para deixar mais patente que existe a possibilidade de termos uma chave primária composta. Se não sabemos exatamente como será a estrutura do arquivo, é interessante incluirmos um espaço já que os registros são fixos. Alterações posteriores implicariam em fazer um programa para mover os dados da estrutura antiga para a nova. Apesar de não ser difícil é uma etapa desnecessária. O campo FILLER (poderíamos usar um nome como fs-reserva mas filler é mais comum) serve apenas para reservar espaço caso desejarmos adicionar mais alguma informação ao registro. Neste caso, reservamos um espaço de 20 caracteres que poderão ser utilizados posteriormente definido-se os novos campos. Por exemplo, podemos incluir o campo fs-uf pic x(02) e deixamos e alteramos filler para pic x(18).

NIVEL 88 (WS-OPCAO)

Especifica nomes condicionados ao valor da variável melhorando a legibilidade do programa. No caso de ws-opcao, se o usuários selecionar a opção 1 para incluir, é muito mais legível eu verificar if e-incluir do que if ws-opcao=1. É possível especificar mais de um nome como no caso de e-encerrar onde o usuário pode digitar um X maiúsculo ou minúsculo. Também é possível especificar uma faixa. Poderíamos ter e-numerico value is 0 thru 9.

COPY screenio.

Apenas inclui o código de retorno quando o usuário pressionar teclas especiais como Esc, F1, etc.. Utiliza o nível 78 (semelhante ao 88) para deixar o programa mais legível. Temos, por exemplo, 78 COB-SRC-ESC VALUE 2005. e é utilizado cob-src-esc nas comparações em vez de 2005 que não significa nada.

SCREEN SECTION

Aqui definimos as diversas telas do nosso programa. Podemos utilizar display nome que irá apenas mostrar a tela ou accept tela que irá mostrar e solicitar a entrada dos dados existentes. Vale lembra que enter confirma a entrada. Para mudar de um campo para outro é utilizado a tecla Tab (próximo campo) ou Shif+Tab (campo anterior)

Em ss-cls limpamos a tela e mostramos um cabeçalho informando a opção sendo executada (menu, inclusão, exclusão, etc.) e um rodapé que poderá conter alguma informação adicional na última linha. Utilizamos a variável ws-numl que é definida em tempo de execução.

Em ss-menu temos a definição do menu do programa. O auto utilizado na entrada de ws-opcao permite que não seja necessário pressionar enter. Quando o campo é preenchido o programa segue adiante automaticamente. Quando a entrada possui mais de um caractere, use com cautela para que o usuário não se confunda ou seja induzido a erro. Aqui seria mais interessante usar LINE PLUS 1 em vez de informar diretamente o número da linha. Assim, permitiria um deslocamento do menu para cima ou para baixo com mais facilidade.

Em ss-tela-registro temos a definição da tela de entrada para os dados do cliente. Temos níveis diferentes principalmente pela necessidade da entrada apenas da chave para alteração ou inclusão. Não faz muito sentido pedir a entrada de nome e endereço se precisamos apenas do telefone (chave do arquivo). Como observações temos a definição da cor diferente para a entrada do telefone que é utilizada por todos os níveis inferiores. E também dois dos problemas que considero no gnuCOBOL 1 (ainda não solucionados na versão 2). O primeiro é a impossibilidade de entrada da direita para a esquerda que é interessante para valores númericos bem como a limpeza da variável se o primeiro caractere digitado não for de movimentação (setas). O segundo é a não verificação de REQUIRED na entrada dos dados implicando que o usuário faça a consistência via programação, isto é, não tem sentido um cadastro de clientes sem o nome do cliente.

Finalmente temos ss-erro que mostra na linha de rodapé uma mensagem de erro e espera que o usuário pressione enter.

INICIO

Informamos algumas variáveis de ambiente para o programa se comportar como desejamos. No caso, queremos que ele interprete as teclas especiais (principalmente Esc). Como o tempo de espera é muito pequena (1 segundo) e a velocidade dos terminais hoje é muito maior, definimos o tempo em milisegundos (poderíamos ter definido no próprio ambiente, no caso do Linux EXPORT escdelay=25, por exemplo) . Depois obtemos o número de linhas e colunas disponíveis no termina e abrimos o arquivo (que será visto posteriormente).

O próximo passo é o laço para que o usuário selecione a opção desejada e o programa execute a rotina correspondente. Ajustamos e mostramos o cabeçalho e rodapé e esperamos a digitação por parte do usuário. Note que usamos diretamente accept para a tela menu dispensando o display.

Como o ponto encerra uma sentença, é importante que se encerre determinados comandos com o respectivo end (end-if, end-evaluate, etc.). Assim, o compilador emitirá um aviso se alguma coisa estiver errada antes do programador queimar neurônios buscando um erro. Um fácil de cometer é um if com diversos comandos internos e, se existir um ponto antes do desejado, o if também terminará antes. O perform parágrafo thru parágrafo também é importante para que a rotina não seja encerrada antes do desejado.

FINALIZA

Basicamente fecha o arquivo e encerra o programa.

INCLUI

São as rotinas de inclusão dos clientes. Inicialmente ajusta e mostra a tela e entra em um loop para a inclusão do cliente. Foi utilizado GO (TO) mas, para quem acha que não pode, poderia utilizar um PERFORM como no loop do menu em inicio utilizando COB-SRC-ESC com indicador de término.

Na parte de gravação write, não foram feitas maiores verificações a assume-se que um erro seja porque a chave já existe. Note que quando você grava (write) informa o registro (file1-rec) e quando le ou exclui (read ou delete) informa o arquivo (file1).

CONSULTA

Basicamente o mesmo processo de INCLUI porém, como as rotinas de alteração, consulta e exclusão utilizam o mesmo princípio, isto é, apenas a entrada do telefone (chave), foi criada uma rotina para evitar a duplicação do processo no programa. Caso seja pressionado Esc durante a entrada da chave, a rotina coloca 99 em fs-stat indicando fs-cancela e o programa sabe que deve cancelar. Apenas para digitar menos e não precisar verificar cob-src-esc.

ALTERA

Semelhante a CONSULTA porém, após a entrada da chave, solicita a entrada do dados do cliente para a alteração. Todo o registro é regravado.

EXCLUI

Semelhante a rotina de consulta porém, após a entrada da chave a rotina mostra os dados do cliente e solicita a confirmação para a exclusão. Qualquer coisa diferente de sim (S ou s) cancela a exclusão. Só entra no laço se a chave não existe caso contrário, retorna ao menu.

LE-CLIENTE

Espera que o usuário informe a chave. Caso seja pressionado Esc, o programa retorna ajustando fs-stat para fs-cancela. Se ocorreu um erro, assume que o cliente não existe, mostra a mensagem e retorna.

ABRIR-ARQUIVOS.

Como a abertura de um arquivo para entrada e saída (I-O) pressupõe que o arquivo já exista (se for apenas para leitura também). Caso ele não exista (erro 35) o programa abre o arquivo para saída (sempre será sobrescrito), fecha e abre novamente para entrada e saída. Se deixarmos apenas para saída (output) o nosso programa só funcionar para a inclusão de clientes mas precisamos consulta, etc.. Não testamos outros erros e, nestes casos, o nosso programa não funcionará de forma adequada.

MOSTRA-ERRO

Mostra a mensagem de erro no rodapé, espera que o usuário pressione enter (ou alguma tecla e enter) e retorna o rodapé para a mensagem anterior.

Considerações finais

  • Note que você pode facilmente copiar o fonte e, com pequenas alterações, adaptar o programa para outra finalidade. Cadastro de fornecedores, por exemplo. Para facilitar ainda mais, poderíamos mover as mensagens de erro para a working-storage section onde o agrupamento permitiria as alterações com mais facilidade. Basicamente trocar cliente por fornecedor no texto.
  • A inclusão de novos campos ao registro também pode ser feito de forma relativamente simples. Basta adicioná-los ao file1-rec e inclui-os na ss-tela-registro. Como a cláusula required é aceita mas ignorada, será necessário efetuar os testes desejados na inclusão.