Introdução à ReasonML

Estas são as notas de uma palestra sobre Reason que apresentei Sábado num evento de Python em São Carlos, o Caipyra 2019. Gostei muito de preparar, por isto compartilho aqui :)


MODELANDO UM POS

introdução à ReasonML

Slides em: https://spark.adobe.com/page/c0eBqll00dyB6/


Bom dia.

Gostaria de agraecer ao convite e dizer que para mim é um prazer muito grande estar de novo aqui no Caipyra, ano passado foi o meu primeiro, eu estava com o pé quebrado e sofri um pouco para subir e descer esta rampa atender aos coffee breaks, mas apesar da mobilidade zoada eu me senti muito bem acolhido, aproveitei muito os dois dias de evento. Espero que os que estiverem aqui este ano aproveitem tanto quanto eu aproveitei ano passado.


SOBRE MIM

Antes de começar acho que vale uma breve apresentação e falar um pouco sobre o meu passado.

Meu nome é Fabricio, e minha experiencia maior sempre foi com web, comecei a carreira há uns 20 anos atrás e sempre mexi com Javascript, eu tive o privilegio de poder acompanhar a evolução tanto da linguagem quanto da comunidade quanto da cultura desta tribo.


JAVASCRIPT

Javascript é uma ferramenta fantástica, já me deu muita alegria, muitas oportunidades, me fez conhecer muita gente brilhante, me proporcionou grandes amizades. Acredito que ela tenha sido para muitas outras pessoas, a porta de entrada para o mundo profissional também.

Ela é uma ferramenta versátil, que permite criar. Entregar. Resolver problemas. O fato de voce poder ver o resultado do que você está criando é algo muito prazeiroso, abrir o developer tools do navegador e mudar em tempo real várias coisas, refinar, ajustar é demais.

Fora toda uma cultura. De gente que compartilha scripts, hacks, e soluções uns com os outros, gente que faz polyfill para o site rodar em navegador antigo, faz plugin de babel para poder testar e usar sintaxes novas sem ter que esperar ela ser adotada pelo navegado ou mesmo a própria linguagem se atualiza, enfim: gente que se adapta, gente que faz tradeoffs entre o lindo teórico e o que é possível de entregar.

É gente inquieta para caramba, que reinventa várias rodas, várias vezes, gente imatura e inocente no bom sentido, pois de vez em quando esta inquietude/experimentação essa inocencia, aliada a necessidade faz esbarrar em coisas genuinamente legais.


PYTHON

Mais recentemente aumentei meu contato com a linguagem Python e seu ecossistema, pois as duas últimas empresas onde trabalhei possuem um DNA muito forte de Python nelas: a CraveFood e atualmente a Stoq Tecnologia aqui em São Carlos.

Eu reconheço que a cultura dos Pythonistas é um pouco outra, que vem de um lugar um pouco diferente, mas não muito, eu consigo enxergar muitas semelhanças e características em comuns entre essas duas tribos, se é que faz algum sentido separar... mas talvez até faça algum sentido, pois querendo ou não, a linguagem acaba influenciando a maneira como a gente pensa.

E eu botei essa capa do Chico aqui, justamente para falar um pouco mais da outra face dessas linguagens versáteis e poderosas...


RUBIKS CUBE

Da mesma forma que javascript já me deu muitas alegrias, já me deu muita tristeza também, já me frustrou demais, fez por algumas vezes querer chorar, querer destruir o teclado, me fez duvidar da minha capacidade, me sentir um burro, um lixo, já me estressou demais, já pensei em largar tudo e mudar de área.


UNDEFINED IS NOT A FUNCTION

Tanto Python quanto Javascript, são ferramentas poderosas, e super flexíveis. Te dão agilidade, permitem chegar longe rápido, fazer muito. Coloquei esta foto aqui pois acho que a analogia da Faca do Chefe é válida, uma faca bem afiada, nas mãos de quem sabe usar é uma grande ferramenta, traz agilidade.


INSECURE STRING PICKLE

Porém sem uma disciplina lascada, vc pode acabar se cortando, acabar num labirinto de espaguete conforme o código vai crescendo e acumulando complexidade.


TAMING THE META LANGUAGE

Sem muitas restrições por parte das linguagens, o jeito de se ter uma boa diligência pode ser com a ajuda de boas práticas, ferramentas, experiencia, testes, boa documentação, processos, e outras maneiras de manter o débito tecnico sob controle.

Tem uma palestra muito boa, mas muito boa mesmo, que eu não canso de recomendar, que chama Taming the Meta Language. Me identifiquei demais e comecei a me interessar por Reason e Ocaml por conta dela. Por favor assistam. O tema central é sobre como trazer as boas praticas e as restrições que ajudam a descrever o sistema para dentro da linguagem pode ser benefico para todos, e como uma linguagem mais robusta pode preencher este papel.


OCAML

Finalmente chegamos então no nosso exercicio proposto: Hoje nós vamos

construir uma aplicação pequena, mas utilizando uma outra linguagem, vamos sair um pouco da nossa zona de conforto, vamos fazer em Ocaml para conhecer o que ela tem de bom.


REASON

Mas o titulo da talk diz ReasonML, sim, vai ser em Reason por questões de ergonomia e familiaridade. Reason é uma sintaxe para Ocaml, um pouco mais próxima do jeitão da sintaxe de Javascript com ES6, mas que por baixo é Ocaml.


POS SYSTEM

A sigla vem do inglês [Point of Sale][pos]. Ponto de Venda, ou frente de caixa.


PDV BACIO DI LATTE

Aqui tem duas imagens de um sistema de PDV de gelateria, basicamente o aplicativo lista os produtos disponiveis, permite ir adicionando numa lista de compras e da um total do quanto ficou.

Sistemas deste tipo podem ser tão simples quanto uma calculadora de bobina e uma tabela de preços, e geralmente nossa intuição de leigo otimista pode nos levar a pensar que são sistemas triviais.

Mas na vida real este é um tipo de sistema que pode crescer em complexidade bastante, se você começar a integrar com dispositivos de hardware, aumentar as opções de pagamentos, calcular impostos, introduzir autenticação do funcionário, um menu para o gerente acessar funções admnistrativas, emissão de nota fiscal eletrônica, programa de fidelidade, suporte a criptomoedas e etc...

Para fins desta apresentação, vamos focar nos casos mais simples possiveis, ja

que a proposta é usar este exemplo como pano de fundo para mostrar as caracteristicas interessantes da linguagem Reason.


DOMAIN MODELING

Existe uma abordagem de desenvolvimento de software chamada Domain Driven Design que prega, entre outras coisas, que o mapa conceitual do dominio do problema deve ser construído junto a um especialista deste domínio e que os engenheiros responsáveis pela construção da solução usem os mesmos termos deste especialista, uma linguagem em comum.


ABSTRACT TYPES

Em linguagens com um sistema de tipos poderoso, tais como Ocaml, Scala e F#, você pode escrever este modelo usando tipos, revisar com o especialista do domínio e depois ter garantias do compilador que a implementação está seguindo os tipos definidos neste modelo e cobrindo exaustivamente todos os casos.

Em outras palavras: a própria documentação é compilada no código.

Neste primeiro exemplo temos o inicio da modelagem do domínio do nosso PDV simples, para hoje vamos começar com estas 3 entidades: venda, pagamento e produto.

Usando tipos abstratos nós conseguimos usar este conceito de linguagem ubíqua e definir nossos requisitos usando a mesma linguagem do especialista do domínio, ele não precisa saber o que é um float, um int, um byte, uma string.

type product;  
type payment;  
type sale;  

TAGGED UNION

Vamos expandir uma dessas 3 entidades, produto. Vamos ensinar pro compilador o cardapio da nossa loja.

Este primeiro tipo funciona como um enum, é uma soma de varios tipos, é um jeito de indicar que aquilo pode ser uma coisa, ou outra coisa, ou outra coisa.

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

type payment;

type sale;  

RECORDS

Vamos expandir agora a venda, vamos usar um record para indicar que uma venda é composta de uma lista de produtos e uma lista de pagamentos.

Records em Reason são como dicionários no Python, ou objetos no Javascript, uma estrutura chave-valor. Records em Reason são imutáveis e exigem uma definição dos tipos dos campos.

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

type payment;

type sale = {  
  products: list(product),
  payments: list(payment),
};

TYPE ARGUMENTS

Para expandir o tipo pagamento, vamos usar um tipo variant também, pois o pagamento pode ser em dinheiro ou cartão de crédito por exemplo. Mas vamos fazer cada caso ter argumentos diferentes, sim, tipos podem receber argumentos em Ocaml, entao se um pagamento for em dinheiro vamos dizer que só nos interessa a quantia, mas que se for cartão queremos também saber a bandeira do cartão.

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

type amount;  
type provider;  
type payment =  
  | Cash(amount)
  | CreditCard(provider, amount);

type sale = {  
  products: list(product),
  payments: list(payment),
};

MODULES

No exemplo anterior criamos dois abstract types para ajudar a descrever o pagamento, quantia e bandeira (amount e provider). Amount e provider são palavras que podem significar outras coisas em outros contextos, portanto vamos aproveitar um outro conceito de DDD que sao os "bounded context" e delimitar um contexto de pagamento, usando um modulo para isso:

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

module Payment = {  
  type amount;
  type provider;
  type t =
    | Cash(amount)
    | CreditCard(provider, amount);
};

type sale = {  
  products: list(product),
  payments: list(Payment.t),
};

Em Ocaml, todo arquivo é um modulo, e você não precisa importar explicitamente um módulo, basta usar o nome do arquivo como nome do modulo. E mais interessante ainda, módulos ou arquivos são cidadãos de primeira classe na linguagem, o que significa que vc pode fazer uma função que retorna um módulo por exemplo. Ha outra palestra boa do Cheng Lou que é especificamente sobre isso: https://youtu.be/24S5u_4gx7w

Aqui eu fiz uma mudança quando encapsulei o pagamento dentro de um arquivo/modulo, que foi usar uma convenção de usar o nome t para o tipo do módulo.


IMPLEMENTATION

Antes de passar para a parte de implementação em si, vale a pena dar uma olhada no que temos até agora e mais uma vez admirar a utilidade de tipos abstratos, toda a parte de modelagem está no código, compila e é legivel para o especialista do domínio. A documentação é o código, o modelo conceitual é quem vai guiar a implementação.

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

module Payment = {  
  type amount;
  type provider;
  type t =
    | Cash(amount)
    | CreditCard(provider, amount);
};

type sale = {  
  products: list(product),
  payments: list(Payment.t),
};

/* implementation */
let addProduct = (cart, product) => cart @ [product];  
let cart =  
  []
  ->addProduct(GELATO_MEDIO)
  ->addProduct(AGUA_300ML)
->addProduct(CAFE_ESPRESSO);

Para começar criei uma função para adicionar produtos num carrinho ou lista de compras. A sintaxe de declarar funções é praticamente igual ã do Javascript com fat arrow, com a diferença que eu não preciso de uma keyword para o retorno da função como o return, a última expressão da função é o seu retorno.

O arroba é um infix operator para concatenar listas (eu poderia ter usado List.append tb)

Esta função recebe uma lista, um produto novo e retorna outra lista, bem simples, e seguindo os principios de programação funcional, não altera nenhum input, ele sempre retorna uma lista nova.

O operador "first pipe" esta seta fininha, é um pipe mesmo, que direciona a saida de uma função para a entrada de outra função, como primeiro argumento. Como a saida da função addProduct é uma lista de produtos e o primeiro argumento de entrada da função addProduct também é uma lista de produtos, podemos encadear com este operador de first-pipe varios addProduct e ter a lista de compra no final.


PATTERN MATCHING

Continuando na parte de implementação do nosso POS, criei aqui uma função getPrice que utiliza um switch para responder com um preço cada produto.

Como produto é um variant type, meu switch é obrigado a ser exaustivo, o compilador vai me dar um warning se eu esquecer algum dos possíveis casos.

Coloquei também um log para mostrar na tela o output da chamada a esta função para o produto CAFE_ESPRESSO. Como estou usando o Bucklescript para traduzir meu Reason para Javascript no final, posso usar algumas funcionalidades do módulo JS do bucklescript, no caso o Js.log

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

module Payment = {  
  type amount;
  type provider;
  type t =
    | Cash(amount)
    | CreditCard(provider, amount);
};

type sale = {  
  products: list(product),
  payments: list(Payment.t),
};

/* implementation */

let getPrice = product =>  
  switch (product) {
  | GELATO_MEDIO => 14.00
  | GELATO_PEQUENO => 12.00
  | CAFE_ESPRESSO => 6.00
  | AGUA_300ML => 4.00
  };

let addProduct = (cart, product) => cart @ [product];

let cart =  
  []
  ->addProduct(GELATO_MEDIO)
  ->addProduct(AGUA_300ML)
  ->addProduct(CAFE_ESPRESSO);

Js.log(getPrice(CAFE_ESPRESSO));  

PARTIAL APPLICATION

Finalmente o exemplo completo, implementei mais duas funções: sumPrice e evaluateTotal

A segunda é apenas um fold_left da primeira, fold é um outro nome para o reduce o underscore como último argumento significa que ele será passado depois. Reason possui auto-currying :)

type product =  
  | AGUA_300ML
  | GELATO_PEQUENO
  | GELATO_MEDIO
  | CAFE_ESPRESSO;

module Payment = {  
  type amount;
  type provider;
  type t =
    | Cash(amount)
    | CreditCard(provider, amount);
};

type sale = {  
  products: list(product),
  payments: list(Payment.t),
};

/* implementation */

let getPrice = product =>  
  switch (product) {
  | GELATO_MEDIO => 14.00
  | GELATO_PEQUENO => 12.00
  | CAFE_ESPRESSO => 6.00
  | AGUA_300ML => 4.00
  };

let addProduct = (cart, product) => cart @ [product];

let cart =  
  []
  ->addProduct(GELATO_MEDIO)
  ->addProduct(AGUA_300ML)
  ->addProduct(CAFE_ESPRESSO);

let sumPrice = (subtotal, product) => getPrice(product) +. subtotal;  
let evaluateTotal = ListLabels.fold_left(~f=sumPrice, ~init=0.0, _);  
Js.log(evaluateTotal(cart));  

ADDING MACHINE

Não sei se os exmeplos foram o suficiente para dar um gostinho da linguagem, ou se alguém daqui se sentiu enganado achando que o PDV que montamos ficou simples demais. Quero terminar mostrando umas maquinas antigas, computadores primitivos que nossos avós usavam que só tinham a função de somar basicamente.

Recomendo este canal do youtube com reiews de maquinas antigas, a titulo de curiosida.e


OBRIGADO

Aqui estão meus contatos, e o link para o grupo de Telegram da nossa comunidade nascente de Reason e Ocaml de Ribeirão Preto, São Carlos e regiao: SanCaml / Re-beirão