1up4developers logo

1up4developers

Nadando contra o Waterfall. tail -f /mind/realworld >> /blog

Encerramento Do Blog

| | Comments


Amigos, este blog será encerrado no final deste mês :(

Desde 2008 vínhamos compartilhando nossas aventuras e experiências no maravilhoso mundo da programação. Aprendemos muito com esta trajetória, com os comentários que vocês leitores fizeram e com as oportunidades que surgiram através do blog.

Foi uma etapa muito importante e proveitosa em nossas carreiras, mas este ciclo chegou ao fim. E este é o momento de iniciar um novo ciclo em nossas carreiras.

O blog será encerrado, mas continuaremos compartilhando nossas experiências no Twitter, Medium e nos blogs pessoais de cada autor.

Aqui estão nossos feeds de conteúdo agile a partir do próximo mês:

Rodrigo Panachi

Roger Leite

Plinio Balduino

Tony Fabeen

Miguel Horlle

Humberto Bulhões

Rogério Ramos

Continue nadando contra o waterfall e até a proxima. Sucesso!

Type Hints No Clojure - Parte 2

| | Comments


Parte 1: http://1up4dev.org/2015/01/type-hints-no-clojure-parte-1

Quando otimizar

Não adianta sair adicionando type hints no código sem critério. A primeira coisa a ser feita é detectar onde estão os gargalos, os pontos que consomem mais recursos sem trazer resultados imediatos utilizando uma boa ferramenta de ::profiling::.

Uma vez que você localize os pontos lentos da aplicação, adicione a seguinte linha no início do seu código:

1
(set! *warn-on-reflection* true)

Agora o compilador vai nos avisar toda vez que for obrigado a usar reflection para invocar um método ou acessar um membro.

Vamos usar a nossa função upper-case para detectar os pontos que podem ser otimizados com type hinting.

1
2
3
4
5
6
7
(set! *warn-on-reflection* true)

(defn upper-case [text]
  (.toUpperCase text))
; Reflection warning, NO_SOURCE_PATH:2:3 - reference to field 
; toUpperCase can't be resolved.
; #'user/upper-case

Essa é a mensagem do compilador nos avisando que não sabe que tipo contém o método toUpperCase. Como estamos usando toUpperCase em text, vamos adicionar o type hint nele.

Perceba que o erro ocorreu durante a compilação da função, e não durante sua execução. Lembre-se que todo código executado já foi compilado em algum momento, incluindo o código que escrevemos no REPL.

1
2
3
4
5
6
(defn upper-case-2 [^String text]
  (.toUpperCase text))
; #'user/upper-case-2

(upper-case-2 "umba umba umba ê")
; "UMBA UMBA UMBA Ê"

Sem erros nem avisos, resolvemos a questão do reflection na expressão.

Falamos sobre otimização e gargalos mas não mostramos na prática o que isso significa.

Vamos posicionar as duas funções, com e sem type hint e executar uma iteração apresentando o tempo gasto com cada versão.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defn upper-case [texto]
  (.toUpperCase texto))
; Reflection warning, NO_SOURCE_PATH:2:3 - reference to field 
; toUpperCase can't be resolved.
; #'user/upper-case

(defn upper-case-2 [^String texto]
  (.toUpperCase texto))
; #'user/upper-case-2

(time (dotimes [_ 100000] (upper-case "mandarina")))
; "Elapsed time: 327.720765 msecs"

(time (dotimes [_ 100000] (upper-case-2 "mandarina")))
; "Elapsed time: 55.403849 msecs"

Nesse nosso exemplo simples o processamento ficou de seis a sete vezes mais rápido.

(Trecho do meu livro “Clojure: Programação Funcional Descomplicada para a JVM”, a ser publicado em breve)

Type Hints No Clojure - Parte 1

| | Comments


Parte 2: http://1up4dev.org/2015/02/type-hints-no-clojure-parte-2

Otimizando com type hints

Por padrão você não informa os tipos dos dados ao Clojure. Internamente o dado vai ser tratado como Object, que é a classe base de qualquer objeto Java.

Qualquer método ou propriedade que você invocar desse objeto será localizado e chamado através de reflection. No Java, reflection é a capacidade de acessar métodos e membros de um objeto ou classe através de metaprogramação.

Exemplificando, esse código Clojure:

1
2
3
4
5
(defn upper-case [text]
  (.toUpperCase text))

(upper-case "umba umba umba ê")
; "UMBA UMBA UMBA Ê"

Vai ser transformado em algo equivalente a isso:

1
2
3
4
5
6
7
8
9
public Object invoke(Object par01) {
  return (Object)(par01
                    .getClass()
                    .getDeclaredMethod("toUpperCase", null)
                    .invoke(par01));
}

(new user$upper_case()).invoke("umba umba umba ê");
// "UMBA UMBA UMBA Ê"

Eu disse equivalente porque o seu código Clojure vai ser compilado diretamente para bytecode ao invés de ser convertido primeiro para código Java.

Como podemos notar, além de termos que invocar três métodos para fazer a chamada de um e termos que fazer typecasting – ou coerção de tipos, se preferir – para que tudo seja tratado como Object, o próprio processo de reflection é lento por si só.

Note como informamos o nome do método toUpperCase como um texto. Com isso o método getDeclaredMethod vai pesquisar uma tabela interna daquela objeto comparando cada nome de método até encontrar o que procuramos.

Para ajudar, como o Clojure não tem a mais remota ideia do que queremos transformar em letras maiúsculas, a função aceita qualquer coisa.

1
2
3
(upper-case 3.14159)
; IllegalArgumentException No matching field found: toUpperCase for 
; class java.lang.Double

Ah, então o compilador do Clojure é mal feito?

Não. Acontece algo muito parecido quando escrevemos código JavaScript, Ruby, Python ou qualquer outra linguagem dinâmica para a JVM. Como você não informou o tipo, o compilador tem que adivinhar ou confiar cegamente no que você está dizendo.

Porém, existe uma forma de diminuir essa trabalheira toda dando dicas ao compilador sobre o tipo de dado que ele deve utilizar. Essas dicas chamam-se type hints, ou dicas de tipos.

Mas atenção: que fique claro que estamos dando dicas ao compilador ao invés de declararmos estaticamente o tipo de dados que estamos utilizando.

Podemos reescrever nosso código dessa forma para que o compilador receba nossas dicas:

1
2
3
4
5
(defn upper-case [^String text]
  (.toUpperCase text))

(upper-case "umba umba umba ê")
; "UMBA UMBA UMBA Ê"

Se tentarmos passar qualquer coisa diferente de String, a JVM vai reclamar na mesma hora.

1
2
3
(upper-case 3.14159)
; ClassCastException java.lang.Double cannot be cast to 
; java.lang.String  user/upper-case (NO_SOURCE_FILE:2)

Aí você pensa ah, tá. era para ter passado String e passei Double. Bonito, não?

Mas não é só isso. Ao usar type hints você ainda leva uma otimização de código totalmente de graça.

O bytecode gerado fica equivalente a esse código Java:

1
2
3
4
5
6
public Object invoke(Object par01) {
  return ((String)par01).toUpperCase();
}

(new user$upper_case()).invoke("umba umba umba ê");
// "UMBA UMBA UMBA Ê"

Se você imaginou que o código, além de menor, também ficou mais rápido, acertou.

Então vou usar isso no meu código inteiro!

Calma lá. Clojure não deixa de ser uma linguagem dinâmica apenas por ter type hints. A contrapartida dessas dicas é que elas poluem o código e, muitas vezes, e dificilmente você vai precisar que todo seu código seja otimizado.

Na próxima parte vamos aprender quando e como otimizar para praticamente todas as situações.

Até lá.

(Trecho do meu livro “Clojure: Programação Funcional Descomplicada para a JVM”, a ser publicado em breve)

Lendo Arquivo CSV Com Parcimônia No Ruby

| | Comments


Ler e escrever arquivo csv é um mal necessário de muitos sistemas, ainda mais levando em conta que esta integração será feita via Excel, em algum Windows, com quilos de texto com acentos e dados a formatar. Dado este cenário, e que ele provavelmente se repetirá no futuro, deixo aqui um post auto-ajuda para mim mesmo e provavelmente para você que está lendo. :D

Na versão 1.9.3 e superior, o Ruby incluiu a classe CSV na sua standard lib, que facilita o trabalho de ler e/ou escrever arquivos csv. Exemplos em código abaixo.

Conhecendo o CSV

O modo mais simples e direto para ler um arquivo csv, é usar o CSV.read que retorna um Array de Arrays:

1
2
3
4
5
require 'csv'
array_students = CSV.read('/tmp/mock_data.csv') # return an Array of Arrays
array_students.each { |row| puts row.inspect }  # => output:
# "[\"id\", \"name\", \"country\", \"birthday\"]"
# "[\"1\", \"Virginia Harvey\", \"GB\", \"01/06/1993\"]"

Dentro da classe CSV, existem mais duas classes que facilitam ainda mais o manuseio dos dados.

Caso necessite de mais requinte e sofisticação, o método CSV.table retorna uma instância de CSV::Table. Com o table, você tem acesso ao cabeçalho através do headers e acesso a cada linha do arquivo com o each, que retorna uma instância de CSV::Row.

1
2
3
4
5
6
7
require 'csv'
table_students = CSV.table('/tmp/mock_data.csv') # => instance of CSV::Table
puts table_students.headers.inspect # => [:id, :name, :country, :birthday]
table_students.each { |row| puts row.inspect } # => output:
# <CSV::Row id:1 name:"Virginia Harvey" country:"GB" birthday:"01/06/1993">
table_students.each { |row| puts row.fetch(:name) } # => output:
# Virginia Harvey

Tanto o read quanto o table, aceitam um hash de options como segundo argumento. Tem uma descrição detalhada na documentação do método new. Exemplo usando options:

1
2
3
require 'csv'
table_students = CSV.table('/tmp/mock_data2.csv', col_sep: ";", skip_blanks: true, converters: [])
table_students.each { |row| puts row.inspect }

CSV converters

CSV::HeaderConverters contém um hash de symbol e block que são usados para converter os valores do cabeçalho. Para usá-los, você deve informar qual converter deseja aplicar na opção header_converters. Acredito que o código abaixo explica melhor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'csv'
puts CSV::HeaderConverters.keys.inspect # => [:downcase, :symbol]

# Add new header converter
CSV::HeaderConverters[:remap] = lambda do |raw_value|
  raw_value = raw_value.to_sym
  case raw_value
  when :country
    :pais
  when :birthday
    :dt_nascimento
  else
    raw_value
  end
end

table_students = CSV.table('mock_data.csv', col_sep: ",", header_converters: :remap)
table_students.each do |row|
  puts [row.fetch(:pais), row.fetch(:dt_nascimento)].inspect # => ["GB", "01/06/1993"]
end

No exemplo acima, criei o HeaderConverter “remap” que traduz o cabeçalho country para pais e birthday para dt_nascimento. Por padrão, o CSV disponibiliza os converters downcase e symbol, que por sinal são usados quando usamos o método table para ler csv.

CSV::Converters segue o mesmo padrão de symbol e block, a única diferença que este é usado para converter os valores da linha. Vamos ao código.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
require 'csv'
require 'date'

puts CSV::Converters.keys.inspect       # => [:integer, :float, :numeric, :date, :date_time, :all]

# Add new converter
CSV::Converters[:nil_to_empty] = lambda do |raw_value|
  raw_value.nil? ? "" : raw_value
end

# Add new converter
CSV::Converters[:brazilian_date] = lambda do |raw_value|
  if raw_value =~ /\d{2}\/\d{2}\/\d{4}/
    Date.strptime(raw_value, "%d/%m/%Y")
  else
    raw_value
  end
end

# Group converters
CSV::Converters[:my_custom_converters] = [:nil_to_empty, :brazilian_date]

table_students = CSV.table('mock_data.csv', col_sep: ",", converters: :my_custom_converters)
table_students.each do |row|
  puts [row.fetch(:country), row.fetch(:birthday)].inspect # => ["GB", #<Date: 1993-06-01 ((2449140j,0s,0n),+0s,2299161j)>]
end

No exemplo acima criei dois converters. Um para trocar nil por “” e o outro que converte para Date caso o valor esteja no formato 99/99/9999.

Encoding hell com Excel

Normalmente o csv é usado como meio de integração Excel <=> Sistema. Acontece que o Excel não se dá muito bem com acentos especiais como ãõáé etc. Isto porque estamos em 2014. Acontece que quando há caracteres especiais, a única abordagem que funcionou foi exportar para Unicode text. Neste formato, o encoding do arquivo é UTF-16LE e separado por tab (\t). Este post de 2009 da Plataformatec explica com mais detalhes este jeitinho do Excel de ser com os dados. A única diferença de 2009 pra hoje, é que podemos passar o encoding como parâmetro ao ler o arquivo, e por sorte evitar o uso do iconv. Vamos ao código:

1
2
3
4
5
6
7
8
require 'csv'

table = CSV.table('mock_unicode.txt',
                  col_sep: "\t", # tab as delimiter
                  encoding: "UTF-16LE:UTF-8") # read UTF-16LE and convert to UTF-8
table.each do |row|
  puts row.inspect
end

Evitando o abuso de memória

Ao ler arquivos com read ou table, o arquivo é colocado em memória, ou seja, ao processar uma planilha de 100mb, o seu processo ruby vai pra um 100mb e pouco. Agora imagina 20 workers e cada um processando uma planilha de 100mb ou mais, facilmente o seu servidor terá um pico de consumo de memória, o no pior cenário vai dar crash no processo. Para evitar este consumo devemos usar o foreach do CSV.

1
2
3
4
5
6
7
8
9
require 'csv'

CSV.foreach("mock_data.csv", col_sep: ",") do |row|
  puts row.inspect
end

# =>
# ["id", "name", "country", "birthday"]
# ["1", "Virginia Harvey", "GB", "01/06/1993"]

Desta maneira a leitura é mais otimizada, pois apenas uma linha por vez é lida. O único problema é que perdemos algumas facilidades do table, como os headers e a instância do CSV::Row por linha. Tentando chegar no modelo ideal, montei uma classe que usa o foreach e mesmo assim tem os headers e os rows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
require 'csv'

class SheetReader

  attr_reader :headers

  def initialize(filepath)
    # options to read unicode text file
    # options = {
    #   col_sep: "\t",
    #   skip_blanks: true,
    #   encoding: "UTF-16LE:UTF-8",
    #   converters: []
    # }
    options = {col_sep: ",", converters: []}
    @csv_reader = CSV.foreach(filepath, options) # gets a iterator
    @headers    = convert_headers(@csv_reader.next) # read first line
  end

  # yield an instance of http://ruby-doc.org/stdlib-2.1.0/libdoc/csv/rdoc/CSV/Row.html
  def each_row(&block)
    begin
      while true
        raw_row = @csv_reader.next  # raise StopIteration in EOF
        yield CSV::Row.new(headers, raw_row)
      end
    rescue StopIteration
    end
  end

  protected

  # Internal: Convert headers to Array of symbols.
  #
  # raw_headers - Array of Strings.
  #
  # Examples
  #
  #   convert_headers(["ATIVO", "NOME COMERCIAL"])
  #   # => [:ativo, :nome_comercial]
  #
  # Returns Array of symbols.
  def convert_headers(raw_headers)
    raw_headers.compact! # removes nil values
    converter = lambda do |header|
      header_converters = CSV::HeaderConverters.values
      header_converters.inject(header) do |header, converter_proc|
        converter_proc.call(header)
      end
    end

    raw_headers.map { |header| converter.call(header) }
  end
end

reader = SheetReader.new('mock_data.csv')
reader.headers # => [:id, :name, :country, :birthday]
reader.each_row do |row|
  puts row.inspect # => #<CSV::Row id:"1" name:"Virginia Harvey" country:"GB" birthday:"01/06/1993">
end

Por último, uma observação importante: todo este código acima foi rodado no ruby 2.1.0. Espero que este mini guia de como ler arquivo csv com Ruby te ajude. Segue alguns links com mais informações:

Dúvidas, sugestões ou qualquer outra coisa. Deixe um comentário ou se preferir, mande um tweety! :D

Extraindo Dados Da Internet Com Clojure

| | Comments


O problema

A Internet é um repositórios de dados gigantesco e frequentemente precisamos extrair algo que nos interessa de maneira automatizada. O grande problema é que esses dados normalmente são apresentados de forma não estruturada, e precisamos utilizar uma técnica chamada scrapping, que consiste em abrir uma página, carregar o HTML e navegar dentro desse código para extrair o que precisamos.

Com o uso das ferramentas certas isso não é complicado, mas pode ser trabalhoso e, uma vez que a página que você estiver lendo altere alguma coisa em sua estrutura, você terá que adaptar seu código às mudanças.

Vamos apresentar um exemplo simples, mas que vai te dar uma boa base de como extrair dados de uma página utilizando Clojure.

Quem escreveu mais livros na Casa do Código?

A Casa do Código é uma editora brasileira especializada em livros para desenvolvedores de software, empreendedores e webdesigners. Seus autores são profissionais conhecidos em suas respectivas áreas e precisamos saber quais deles escreveram mais livros.

Para isso, vamos abrir a página inicial da editora em http://www.casadocodigo.com.br/, que contém links para todos os livros publicados até o momento. Com esses links em mãos, vamos entrar em cada um deles e extrair os nomes dos autores para em seguida agrupá-los e apresentarmos o resultado.

Como fazer

É necessário ter algum conhecimento de Clojure e Leiningen para poder acompanhar este post. Dê uma lida no texto Fazendo mágica com o REPL do Clojure para aprender como criar um projeto.

Vamos criar um projeto chamado autores, definir o namespace inicial e adicionar a biblioteca Enlive, que vai nos permitir extrair os dados que queremos de dentro do código HTML. O nosso arquivo project.clj vai ficar parecido com o exemplo abaixo:

(defproject autores "0.1.0-SNAPSHOT"
  ; informações de licença e descrição do projeto

  :dependencies [[org.clojure/clojure "1.6.0"]
                 [enlive "1.1.5"]]
  :main autores.core)

Enlive é uma biblioteca criada por Christophe Grand, coautor de Clojure Programming, que permite que você gere código HTML escrevendo em Clojure, e permite também que você extraia textos de um arquivo HTML já existente.

Para utilizarmos o Enlive em nosso código, vamos referenciar as funções na nossa declaração de namespace. Vamos também adicionar o Pretty Print para exibir o resultado formatado e as bibliotecas do clojure.string para manipularmos o texto. Também vamos precisar importar a classe java.net.URL para tratarmos o endereço do site. Nosso código então começa assim:

(ns autores.core
  (:require [clojure.pprint :as pp]
            [net.cgrand.enlive-html :as en]
            [clojure.string :as str])
  (:import  [java.net URL]))

O primeiro passo é varrer a página inicial da editora e extrair os links dos livros. Abrindo o código fonte da página, percebemos que ela tem a seguinte estrutura, que aqui está devidamente resumida para fins de apresentação:

<html>
<head>
  <!-- titulo, meta, etc -->
</head>
<body>
  <nav>
    <!-- menu do topo -->
  </nav>
  <section>
    <!-- links e imagens com os livros -->
  </section>
  <footer>
    <!-- menu do rodapé -->
  </footer>
</body>
</html>

Visualmente, a página tem a aparência da imagem abaixo:

/images/uploads/2014/10/cdc-parts.png

Felizmente a página está bem estruturada e todos os links que nos interessam estão dentro da área section, o que vai nos poupar trabalho.

Vamos criar uma função chamada get-links, que vai receber a URL do site em formato texto e vamos extrair todo o código HTML usando a função html-resource do Enlive.

(defn- get-links [url]
  (en/html-resource (URL. url)))

Perceba que a função html-resource exige que você converta a URL de texto para um objeto java.net.URL, para só então o passarmos como parâmetro para o Enlive.

Para evitar que o encadeamento de muitas funções torne o código difícil de ler, vamos alterá-lo para utilizar o operador -> e vamos guardar o resultado em um binding chamado links. Perceba que essa abordagem permite que possamos adicionar funções no final da lista de argumentos de -> sem diminuir a legibilidade do código.

(defn- get-links [url]
  (let [links (-> url
                  URL.
                  en/html-resource)]
    (pp/pprint links)))

Vamos então usar a função select, também do Enlive, que recebe como argumento um vector com as tags que você deseja extrair. Nós queremos somente os links, formados pela tag <a></a>, que estão contidas entre <section> e </section>.

(defn- get-links [url]
  (let [links (-> url
                  URL. 
                  en/html-resource
                  (en/select [:body :section :a]))]))

A função select vai nos retornar uma lista contendo um map para cada elemento HTML que obedecer aos nossos requisitos. Cada map tem um item :attrs que contém os atributos da tag HTML, incluindo a página para a qual o link está apontando. Ainda dentro da função get-links, vamos converter essa lista de mapas em uma lista que contenha apenas os endereços e para isso vamos usar a função map, que recebe como parâmetros a função que vai transformar cada item da lista e a lista a ser transformada. Para fins de didática, vamos suprimir o código que está dentro de let, para só no final da explicação mostrarmos a função completa.

  (map #((% :attrs) :href) 
       links)

Agora nossa função está retornando uma lista de links como na listagem abaixo, devidamente resumida:

("/products/livro-programador-apaixonado"
 "/products/livro-aspnet-mvc"
 "/products/livro-jpa-eficaz"
 "/products/livro-photoshop"
 "/products/colecao-frameworks-java"
 ...
 "/products/livro-ciencia-da-computacao-com-jogos"
 "/products/vale-presente")

Os links são relativos à URL da página inicial, então vamos adicionar o endereço que que foi passado para a função get-links para torná-los absolutos. Note que nessa lista existem links que não são de livros, mas de coleções e do vale presente. Podemos eliminá-los usando a função filter, que recebe como parâmetros uma função que retorna true ou false de acordo com cada item da lista, e a lista a ser filtrada que é passada como terceiro parâmetro. Os itens que fizerem a função retornar true ficam, e os demais não são incluidos.

(filter
  #(. % contains "livro")
  (map #(str url ((% :attrs) :href)) 
       links))

E agora nossa função retorna os endereços absolutos de todos os livros da editora, conforme a listagem abaixo:

("http://www.casadocodigo.com.br/products/livro-programador-apaixonado"
 "http://www.casadocodigo.com.br/products/livro-aspnet-mvc"
 "http://www.casadocodigo.com.br/products/livro-jpa-eficaz"
 "http://www.casadocodigo.com.br/products/livro-photoshop"
 ...
 "http://www.casadocodigo.com.br/products/livro-ciencia-da-computacao-com-jogos")

Agora que concluímos o primeiro passo da nossa pesquisa, vamos editar o código da nossa função main para que a função get-links seja chamada:

(defn -main [& args]
  (pp/pprint
    (get-links "http://www.casadocodigo.com.br")))

Vamos criar agora uma função chamada get-author, que vai receber cada um dos links e, usando as funções do Enlive que já conhecemos, vai extrair o nome do autor, ou dos autores, de cada um dos livros.

Os nomes dos autores ficam dentro de uma tag span marcada com a classe product-author-link. Mais uma vez o site bem construído nos ajuda na tarefa. Para pesquisarmos uma tag que esteja utilizando determinada classe, vamos separar tag e classe com um ponto final, como se fosse um arquivo CSS. Assim, o nosso span com a classe product-author-link vai virar :span.product-author-link. O código inicial não vai ficar muito diferente do que fizemos em get-links:

(defn- get-author [url]
  (let [authors (-> url
                    URL.
                    en/html-resource
                    (en/select [:span.product-author-link]))]
      (pp/pprint authors)))

Vamos usar o link do primeiro livro para testar. O resultado traz toda a informação da tag span dentro de um map que está dentro de uma lista, e não só o nome do autor.

(get-author "http://www.casadocodigo.com.br/products/livro-programador-apaixonado")

({:tag :span,
  :attrs {:class "product-author-link"},
  :content
  ("\n          \n            Chad Fowler\n          \n        ")})

Novamente vamos suprimir o código que está dentro do let para não poluir o texto. Vamos selecionar o map que está dentro da lista usando a função first e, em seguida, utilizar somente o valor que está na chave :content:

((first authors) :content)

O valor que estava em :content também é uma lista contendo o nome do autor. Além do nome do autor, o texto está poluído com quebras de linhas, simbolizado por \n e espaços em branco. Vamos utilizar a função replace do namespace clojure.string para remover esses caracteres indesejados. Essa função recebe como parâmetros o texto original, uma expressão regular indicando o que deve ser alterado e, por último, o texto a ser utilizado na alteração.

Vamos utilizar o operador -> para facilitar a leitura:

(-> ((first authors) :content)
    first
    (str/replace #"(\n|  )" ""))

Ao executar a função, temos o nome do autor limpo e sem caracteres indesejados:

(get-author "http://www.casadocodigo.com.br/products/livro-programador-apaixonado")
=> "Chad Fowler"

Tudo certo quando estamos lidando com um livro escrito por apenas um autor, mas quando temos coautorias a nossa função já não faz o que é esperado. Por exemplo, no livro de ASP.NET MVC, nossa função retorna o autor como Fabrício Sanchez e Márcio Fábio Althmann, e no livro de Lógica de Programação temos Paulo Silveira, Adriano Almeida. Tanto , como e são usados para separar os nomes dos autores. Vamos fazer a nossa função retornar um vector com os nomes dos autores separados utilizando a função split, do namespace clojure.string. Essa função recebe como argumentos o texto a ser dividido e uma expressão regular indicando onde o texto deve ser dividido:

(-> ((first authors) :content)
    first
    (str/replace #"(\n|  )" "")
    (str/split #"(, | e )"))

Agora temos os nomes dos autores separados corretamente dentro um vector, como no exemplo abaixo:

(get-author "http://www.casadocodigo.com.br/products/livro-programacao")
=> ["Paulo Silveira" "Adriano Almeida"]

Voltando à nossa função -main, vamos fazer com que a função get-author pega os autores de cada um dos links retornados pela função get-links. Vamos usar o operador ->> para deixar mais fácil de entender as transformações que estamos fazendo nos dados. A diferença para o -> é que o operador ->> passa o resultado da expressão como último parâmetro da expressão seguinte, enquanto o operador -> passa como primeiro. Isso é necessário para podermos passar a lista retornada por get-links para a função map da expressão seguinte:

(defn -main [& args]
  (pp/pprint
    (->> "http://www.casadocodigo.com.br"
         get-links
         (map get-author))))

A nossa função -main retornou uma lista contendo vários vector com os nomes dos autores. Um livro com mais de um autor vai retornar um vector com mais de um nome.

(["Chad Fowler"]
 ["Fabrício Sanchez" "Márcio Fábio Althmann"]
 ...
 ["Mauricio Tollin" "Rodrigo Gomes" "Anderson Leite"]
 ...
 ["André Backes"]
 ["Bruno Feijó" "Esteban Clua" "Flávio S. Correa da Silva"])

Vamos utilizar a função flatten para converter essa lista de vectors de diversos tamanhos em uma lista de uma dimensão. Isso vai nos permitir calcular quantas vezes cada nome aparece na lista por meio da função frequencies. Como cada vez que o nome aparece é um livro escrito por aquele autor, podemos entender que frequencies vai atribuir o número de livros que aquele autor tem publicado pela Casa do Código:

(defn -main [& args]
  (pp/pprint
    (->> "http://www.casadocodigo.com.br"
         get-links
         (map get-author)
         flatten
         frequencies)))

Agora temos uma lista não ordenada com o nome do autor e a quantidade de livros publicados:

{"Caio Ribeiro Pereira" 1,
 "Fabrício Sanchez" 1,
 "Alexandre Saudate" 2,
 "Chad Fowler" 1,
 ...
 "Gabriel Schade Cardoso" 1,
 "Guilherme Moreira" 1}

Nosso próximo passo é ordenar essa lista, deixando os autores com maior número de livros no topo. Para isso vamos usar a função sort, que permite que você informe uma função para selecionar o critério de ordenação, que no nosso caso vai ser a função last, que retorna o último item de uma lista, e também a função que vai ser usada para comparar um item com outro e definir quem vem primeiro, que nosso caso vai ser >. No caso do last, convém deixar claro que vamos pegar o último item do que vai ser comparado. No caso de "Caio Ribeiro Pereira" 1, o último item é o número 1. Se quisessemos ordenar por nome, usariámos a função first.

Em seguida vamos dividir essa lista em n grupos, de acordo com a quantidade de livros publicados usando a função partition-by. No nosso exemplo teremos um grupo com autores que tenham dois livros publicados e outro com autores com apenas um livro. Como nos interessa apenas os autores que mais publicaram livros, vamos usar a função first para selecionarmos apenas o primeiro grupo. Com isso nosso código ficará assim:

(defn -main [& args]
  (pp/pprint
    (->> "http://www.casadocodigo.com.br"
         get-links
         (map get-author)
         flatten
         frequencies
         (sort-by last >)
         (partition-by last)
         first)))

Agora temos o resultado abaixo:

(["Alexandre Saudate" 2]
 ["Sérgio Lopes" 2]
 ["Paulo Silveira" 2]
 ["Tárcio Zemel" 2]
 ["Anderson Leite" 2]
 ["Mauricio Aniche" 2]
 ["Gilliard Cordeiro" 2]
 ["Hébert Coelho" 2]
 ["Eduardo Guerra" 2]
 ["Rafael Steil" 2])

Agora que temos a lista dos maiores autores, vamos otimizar um o nosso código adicionando uma dose de processamento paralelo. Note que a função get-links retorna uma lista de endereços que é passada como parâmetro para a função map, que passa endereço por endereço para a função get-author, sequencialmente. Se trocarmos map por pmap, serão criadas threads que executarão get-author em paralelo, melhorando o tempo total do nosso código. A quantidade de thread criadas por pmap está diretamente ligada ao número de núcleos ou processadores que sua máquina tiver.

Porém, o uso de pmap pode trazer um problema: é comum que a aplicação fique congelada por até um minuto após a execução do pmap por conta de questões internas de timeout de threads e configurações do pool utilizado pelo Clojure. Para resolver isso, devemos solicitar ao Clojure que finalize todas as thread que não estiverem sendo usadas, liberando-as para que o programa possa ser encerrado. Para isso usamos a função shutdown-agents na última linha da função -main.

Agora temos o nosso código funcionando alguns segundos mais rápido e trazendo os autores que mais publicaram livros pela Casa do Código.

Código completo

(ns autores.core
  (:require [clojure.pprint :as pp]
            [net.cgrand.enlive-html :as en]
            [clojure.string :as str])
  (:import  [java.net URL]))

(defn- get-links [url]
  (let [links (-> url
                  URL. 
                  en/html-resource
                  (en/select [:body :section :a]))]
      (filter
        #(. % contains "livro")
        (map #(str url ((% :attrs) :href)) 
             links))))

(defn- get-author [url]
  (let [authors (-> url
                    URL.
                    en/html-resource
                    (en/select [:span.product-author-link]))]
    (str/split
      (str/replace
        (first
          ((first authors) :content))
        #"(\n|  )" "")
      #"(, | e )")))

(defn -main [& args]
  (println "Os autores com mais publicações na Casa do Código:")
  (pp/pprint
    (->> "http://www.casadocodigo.com.br"
         get-links
         (pmap get-author)
         flatten
         frequencies
         (sort-by last >)
         (partition-by last)
         first))
  (shutdown-agents))

Fazendo Mágica Com O REPL Do Clojure

| | Comments


Caso você não esteja íntimo com o REPL do Clojure, recomendo a leitura do texto anterior para aprender o básico.

É recomendado também que você leia as mensagens emitidas pela aplicação com a voz do Cid Moreira. Para uma versão em inglês do artigo estou considerando sugerir as vozes do Morgan Freeman ou do Stephen Fry.

As pessoas me perguntam se um REPL é equivalente ao IRB do Ruby ou o modo interativo do Python. No artigo anterior eu comentei que o REPL é bem mais do que isso, mas ainda não fui convincente o bastante.

Nesse artigo eu pretendo demonstrar como podemos modificar a aplicação enquanto ela é executada, sem necessidade de reiniciá-la ou de esperar para que a compilação termine.

Primeiro, vamos instalar uma ferramenta chamada Leiningen, que pode ser facilmente instalada a partir de http://leiningen.org/#install.

O Leiningen é uma ferramenta que automatiza uma série de processos cotidianos do desenvolvimento de uma aplicação, além de também cuidar das dependências da aplicação. Pense nele como um primo bonito do Maven ou do Gradle.

Primeiro, vamos criar uma aplicação usando o Leiningen.

No terminal, digite:

lein new misterm

Se for a primeira vez que você utiliza o Leiningen, algumas dependências serão baixadas para a sua máquina.

$ lein new misterm
Generating a project called misterm based on the 'default' template.
The default template is intended for library projects, not applications.
To see other templates (app, plugin, etc), try `lein help new`.

Será criado um diretório chamado misterm, onde teremos nossa aplicação, e dentro desse diretório, edite um arquivo project.clj.

; O seu arquivo project.clj vai ter essa cara
(defproject misterm "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]])

O conteúdo do arquivo project.clj nada mais é do que um código Clojure válido, sendo defproject uma macro que armazena as configurações do projeto em algum estado global. Vou deixar para explicar macros em outro artigo.

Para que a mágica funcione, vamos adicionar o nREPL na nossa aplicação. nREPL é uma biblioteca que permite que o REPL do Clojure seja acessado remotamente, normalmente através de uma porta TCP. O próprio REPL do Clojure disponibilizado pelo Leiningen já vem com o nREPL, como vamos ver mais para frente.

Para criarmos um projeto executável, precisamos informar no projeto em qual namespace estará o entrypoint da aplicação. Pense num namespace como um package do Java, apesar de existirem grandes diferenças entre ambos. Para a nossa explicação, considerar um como o outro basta.

Vamos editar o arquivo, adicionando a nova dependência dentro do vetor :dependencies e também a opção :main misterm.core antes do parêntese final.

O final do arquivo vai ficar assim:

; final do arquivo project.clj
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [org.clojure/tools.nrepl "0.2.5"]]
  :main misterm.core)

Agora vamos editar o arquivo core.clj, que está dentro de src/misterm. Perceba que o nome da aplicação é usado por padrão como parte do nome do namespace.

Encontraremos um arquivo assim:

(ns misterm.core)

(defn foo
  "I don't do a whole lot."
  [x]
  (println x "Hello, World!"))

Aqui está sendo declarado o namespace misterm.core, e em seguida uma função foo que simplesmente imprime o texto Hello, World!.

Vamos modificar a declaração do namespace para que possamos usar o nREPL em nossa aplicação.

(ns misterm.core
    (:require [clojure.tools.nrepl.server :as nrepl]))

Em seguida vamos iniciar o servidor do nREPL na porta 12345.

(defonce server (nrepl/start-server :port 12345))

Estamos usando defonce aqui para garantir que, ao acessarmos a aplicação via nREPL, o servidor não seja iniciado mais de uma vez. Caso isso aconteça teremos um erro porque a porta 12345 já estará em uso.

Vamos apagar a função foo e vamos criar uma chamada magic:

(defn magica []
  (Thread/sleep 2000)
  (println "Mostre-nos o seu segredo, Mister M"))

Nossa função simplesmente espera dois segundos e, sem seguida, imprime na tela um texto.

Agora vamos criar a função -main, que serve como entrypoint da aplicação, assim como o método main de uma aplicação Java.

(defn -main [& args]
  (loop []
    (magica)
    (recur)))

O argumento & args indica que a função -main aceita uma quantidade indefinida de parâmetros, ou mesmo nenhum, exatamente como acontece com o método main(String ... args) do Java.

Os operadores loop e recur são usados para simularmos tail call recursion no Clojure, o que é assunto para outro texto. Para o que precisamos, basta saber que criamos um loop infinito com eles. Dentro desse loop infinito estamos invocando a função magica.

Tudo pronto, vamos salvar o arquivo e voltar para o terminal. Execute o comando lein run para executar a aplicação e a cada dois segundos será exibida a mensagem. Lembre-se de usar a voz do Cid Moreira ao ler.

$ lein run
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M

Vamos abrir um novo terminal e executar o comando lein repl :connect 127.0.0.1:12345.

$ lein repl :connect 127.0.0.1:12345
Connecting to nREPL at 127.0.0.1:12345
REPL-y 0.3.5, nREPL 0.2.6
Clojure 1.6.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0-b132
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=>

O prompt indica que estamos no namespace padrão do Clojure, chamado user. Vamos para o namespace onde estão as nossas funções para verificar se estamos mesmo conectados na aplicação.

(ns misterm.core)

Agora vamos executar a função magica. Se tudo foi feito corretamente até agora, vamos esperar dois minutos e então ler a mensagem Mostre-nos o seu segredo, Mister M.

(magica)
; Mostre-nos o seu segredo, Mister M

Tudo certo. Agora vamos modificar a aplicação enquanto ela está sendo executada.

Dê uma olhada no primeiro terminal. A essa altura a nossa mensagem já encheu a tela.

De volta ao segundo terminal, o que está com o REPL aberto, reescreva o conteúdo da função magica:

(defn magica []
  (Thread/sleep 1000)
  (println "Aaaaaahhhhh, Mister M!"))

A função magica já existia, mas estamos redefinindo em tempo de execução. Se você tentar fazer isso no IRB, por exemplo, receberá uma mensagem de erro.

Agora olhe novamente no primeiro terminal, aquele que estava com a tela cheia de mensagens.

Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Mostre-nos o seu segredo, Mister M
Aaaaaahhhhh, Mister M!
Aaaaaahhhhh, Mister M!
Aaaaaahhhhh, Mister M!

A mensagem mudou! Não só isso. Agora ela leva um segundo para ser exibida na tela, ao invés dos dois originais. Nós mudamos a aplicação enquanto ela estava sendo executada.

Claro que toda mágica tem lá seus pontos a serem considerados. Se você reiniciar a aplicação a mensagem original será novamente exibida, já que alteramos a aplicação enquanto ela estava sendo executada, e não seu código fonte.

Esse recurso é muito útil para corrigir erros sem derrubar a aplicação, para desenvolver aplicações de maneira verdadeiramente incremental e iterativa, e também para aumentarmos a velocidade do ciclo de desenvolvimento ao máximo. Pense que dessa forma não precisamos mais derrubar a aplicação, recompilar, executar novamente, aguardar o tempo de carga da JVM, que não é pouco. Numa visão otimista, caso você ganhe dez segundos em cada iteração do seu ciclo de desenvolvimento, ao final do dia você terá economizado muito tempo, além de ter mantido o foco naquilo que realmente importa. É sabido que essas mudanças de contexto, por mais rápidas que sejam, atrapalham a concentração e a produtividade.

Editores como Emacs e ViM têm plugins que se conectam ao nREPL, fazendo com que você nem ao menos precise fechar a janela de edição de código para alterar a aplicação. Já o Lighttable oferece um modo chamado InstantREPL, onde seu código é avaliado na própria edição, exibindo os valores enquanto você programa.

O REPL é uma ferramenta poderosa não só no Clojure, mas em praticamente todos os dialetos LISP. Experimente fazer isso com a sua linguagem preferida e veja se consegue ter tanto ganho de produtividade.

P.S.: Jon Garret conseguiu inclusive corrigir um bug em um satélite em órbita graças ao uso do REPL. Leia mais aqui.

Integração Contínua E Entrega Contínua De Modo Fácil Com Wercker

| | Comments


TL;DR Wercker é uma plataforma de integração contínua, baseado em containers e focado em facilitar o build e deploy de aplicações na nuvem. Totalmente integrado com Github, BitBucket, Heroku, AWS e OpenShift, em questão de minutos e com apenas alguns cliques é possível configurar o build e deploy da sua aplicação. O serviço está em beta, permitindo repositórios privados grátis!

Como funciona

Cada vez que você faz um git push, o Wercker recebe um sinal que houve atualização, baixa o código do repositório, monta o ambiente do build definindo variáveis de ambiente, rodando as migrations, etc, e executa os testes ou qualquer outro passo que você definir, como compilar os assets, etc.

Uma vez que o build execute com sucesso, fica disponível para deploy na plataforma que escolher, como Heroku ou AWS. Da mesma forma do build, o deploy consiste em passos definidos, como rodar o Capistrano ou fazer push no repositório do Heroku.

Durante as duas etapas deste processo de build e deploy, o Wercker ainda pode notificar seu HipChat ou Slack :)

Por que é diferente?

Existem vários serviços de CI com a mesma proposta, como o Travis ou o Codeship. Mas o Wercker permite total customização do ambiente do build e do deploy, uma vez que é baseado em containers.

Você pode escolher um container oficial para seu build, como Ubuntu com Ruby instalado, por exemplo, ou pode procurar um container disponibilizado por outro usuário no diretório do Wercker.

Mas se precisar de algo muito específico, você pode criar seu próprio container (ou box) com pacotes e serviços que desejar, versões, etc, e utilizá-lo no seu build. E você também pode disponibilizar este box no diretório do Wercker para outros usuários utilizarem!

Boxes

Um box é um ambiente virtual empacotado e versionado com pacotes e serviços necessários para executar o build. Em outras palavras, é o container “base” que rodará seu build.

Para utilizar um box, basta declarar no início do wercker.yml:

1
box: wercker/ubuntu12.04-ruby2.0.0

Tem box para Ruby, Node.js, Go, Java/Android e até para Docker. Na prática, os boxes oficiais vão atender a maioria dos casos, mas você pode criar seu próprio box como desejar.

Para definir um box, basta criar um repositório com o arquivo wercker-box.yml descrevendo a base (Ubuntu, por exemplo), o provisionamento (Chef, Puppet, etc, e adicioná-lo ao build pipeline do Wercker.

Uma vez que o build passar, é possível fazer o deploy para disponibilizá-lo no diretório do Wercker. Assim, é possível utilizar este box no processo de build da sua aplicação.

Mais informações: http://devcenter.wercker.com/articles/boxes/

Services

Serviços são boxes prontos disponibilizados pelo Wercker, como MySql, PostgreSql, MongoDB, Redis, etc. Em outras palavras, é um container configurado com um serviço ready to use.

Para utilizar um service, basta adicionar ao seu wercker.yml:

1
2
3
4
box: wercker/ruby
services:
    - wercker/mongodb
    - wercker/redis

Mais informações: http://devcenter.wercker.com/articles/services/

Steps

As steps são os “comandos” executados pelo Wercker no build ou deploy da aplicação, como bundle-install, database-migrate, script, etc.

Você pode utilizar as steps disponibilizadas pelo Wercker ou criar suas próprias steps, executando os comandos que julgar necessário no seu build ou deploy.

Para definir as steps do seu build, basta adicionar o wercker.yml:

1
2
3
4
5
6
7
8
9
10
11
12
build:
    steps:
        - rvm-use:
            version: 2.1.2
        - bundle-install
        - rails-database-yml
        - script:
            name: db migrate
            code: bundle exec rake db:migrate
        - script:
            name: rspec
            code: bundle exec rspec

Mais informações: http://devcenter.wercker.com/articles/steps/

Deploy pipeline

O Wercker permite fazer o deploy da sua aplicação em vários PaaS como Heroku automaticamente ou definir o processo de deploy manualmente, utilizando o Capistrano ou executando um script de sua preferência.

Durante a processo de deploy, é possível definir chaves SSH específicas, variáveis de ambiente, etc.

Para configurar seu deploy, adicione ao wercker.yml (por exemplo):

1
2
3
4
5
6
7
8
deploy:
    steps:
        - heroku-deploy
        - s3sync:
            key_id: $AWS_ACCESS_KEY_ID
            key_secret: $AWS_SECRET_ACCESS_KEY
            bucket_url: $AWS_BUCKET_URL
            source_dir: build/

Mais informações: http://devcenter.wercker.com/articles/deployment/

#comofas?

O Wercker é legal, fácil, bla bla bla. Vamos à um exemplo prático, passo-a-passo, de como configurar seu projeto no Wercker, fazer o build e o deploy automaticamente no Heroku.

0 – Crie sua conta :)

1 – Escolha o repositório, adicione as chaves SSH, finalize;

2 – Adicione o arquivo wercker.yml ao seu projeto, parecido com este:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
box: wercker/rvm
services:
    - wercker/postgresql
build:
    steps:
        - rvm-use:
            version: 2.1.2
        - bundle-install
        - rails-database-yml
        - script:
            name: db migrate
            code: bundle exec rake db:migrate RAILS_ENV=test
        - script:
            name: rspec
            code: bundle exec rspec -f d
    after-steps:
        - hipchat-notify:
            token: $HIPCHAT_TOKEN
            room-id: 123456
            from-name: wercker

Mais informações: http://devcenter.wercker.com/articles/werckeryml

3 – Dispare o build com git push;

1
2
git commit -m "Configurando Wercker"
git push origin master

4 – Configure o deploy no Heroku:

5 – Dispare o build/deploy com git push ou manualmente:

6 – \o/

Espalhe a palavra!

Fácil né? Não há mais choro desculpas para não utilizar um CI em seu projeto.

Configure seu projeto, tire um print screen do primeiro build com sucesso e Tuite para o @wercker para receber stickers de grátis!

Compartilhe este post! Dúvidas e sugestões nos comentários abaixo. Sucesso!

Alguns Truques Com O REPL Do Clojure

| | Comments


O REPL é uma das ferramentas mais úteis para se programar em Clojure. Se você está chegando do Ruby ou do Python está mais do que acostumado a usar o IRB ou o modo interativo do Python. Veremos no decorrer do capítulo que o REPL é bem mais do que um prompt da linguagem, que serve apenas para que instruções sejam testadas.

Existem alguns atalhos e funções auxiliares que tornam o uso do REPL bem mais produtivo. Por mais que escrever diretamente no REPL não seja tão confortável quando no seu editor preferido, algumas vezes isso acaba sendo necessário.

Qual é mesmo o nome daquela função?

As funções da biblioteca padrão do Clojure vem com um texto explicativo, onde você pode se situar sobre como utilizá-las.

Podemos pesquisar alguma palavra que estiver dentro desses textos para encontrar a função que queremos, mas não lembramos o nome. Para isso, usamos find-doc, seguido da palavra ou trecho de texto relacionado ao que queremos.

Vamos supor que eu esteja procurando algo sobre sockets. Basta digitar (find-doc “socket”) no REPL.

1
2
3
4
5
6
7
8
9
(find-doc "socket")
; -------------------------
; clojure.tools.nrepl/connect
; ([& {:keys [port host transport-fn], :or {transport-fn
;  transport/bencode, host "localhost"}}])
;  Connects to a socket-based REPL at the given host (defaults to
;  localhost) and port, returning the Transport (by default clojure.

; e mais um monte de coisas

No nosso exemplo, encontramos a função connect, que está no namespace clojure.tools.nrepl.

Se você lembra de alguma parte do nome da função, então pode usar a função apropos, passando como parâmetros o trecho do nome ou uma expressão regular. Não se preocupe com expressões regulares agora, pois veremos esse assunto em detalhes mais para frente.

Vamos supor que eu esteja manipulando vetores e não lembre o nome da função, mas saiba que a estrutura chama-se vector:

1
2
(apropos "vector")
; (vector-of vector vector? vector-zip)

E agora você pode usar a função doc para ver a documentação daquela que mais se parecer com o que você estiver procurando:

1
2
3
4
5
(doc vector?)
; -------------------------
; clojure.core/vector?
; ([x])
;   Return true if x implements IPersistentVector

Existe uma variação de apropos chamada apropos-better, que informa também o namespace da função quando ela não estiver dentro do namespace clojure.core ou dentro do namespace em que você estiver no momento:

1
2
(apropos-better "vector")
; (vector vector-of vector? clojure.zip/vector-zip)

Um pouco de Bash na sua vida

Quando você usa o REPL por dentro do Leiningen, alguns atalhos já conhecidos pelos usuários de Bash estão disponíveis, mesmo para quem está usando o Leiningen no Windows.

O primeiro deles é a tecla TAB, que exibe os nomes de funções que começam com o que você já digitou.

Por exemplo, vou digitar map e pressionar TAB

1
2
(map
; map           map-indexed   map?          mapcat        mapv

Outra combinação que agiliza bastante o trabalho é a combinação Control L, ou Command L se você estiver usando MacOS, que limpa os resultados das expressões anteriores e mantém apenas a expressão que você estiver digitando no momento.

Existe também a combinação Control R, ou Command R, que completa o que você estiver digitando usando o histórico de comandos do REPL. Pressionando essa combinação mais de uma vez vai alternar entre todas as combinações já utilizadas que contenham o texto que você já digitou.

Usar as setas para cima ou para baixo permite que você navegue nos comandos utilizados recentemente.

Recuperando os últimos resultados

Existem também símbolos especiais que guardam os resultados das últimas expressões e exceções. Eles são 1, 2 e 3 para os valores e e para a última exceção, ou erro, que ocorreu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(+ 1 2)
; 3

(* 2 4)
; 8

(/ 8 2)
; 4

(println "Resultados anteriores:" *1 *2 *3)
; Resultados anteriores: 4 8 3

(/ 1 0)
; ArithmeticException Divide by zero

(println "Último erro:" *e)
; Último erro: #<ArithmeticException java.lang.ArithmeticException:
;   Divide by zero>

Consultando o código fonte

Algumas vezes é bom ter acesso ao código fonte de determinada função ou macro para que possamos entender melhor como ela funciona. Enquanto eu escrevia este artigo, fiz isso constantemente para descobrir como as coisas funcionam por baixo dos panos.

Infelizmente, nem sempre é simples ir até o site onde o código fonte do Clojure está disponível e procurar o arquivo em que aquela função está definida.

Pior ainda quando a versão que está lá é diferente da versão que você está usando no momento. E fica ainda pior quando você não tem acesso ao código fonte da biblioteca que estiver utilizando.

Para nos ajudar, existe a macro source, que recebe como parâmetro o nome da função, sem aspas, e exibe o respectivo código fonte, quando possível.

Existem casos em que isso não é possível, como quando você tentar ler o fonte de uma forma especial ou de um código que foi compilado utilizando AOT (veremos isso em detalhes mais para frente).

Vamos exibir o código fonte da função +, responsável por somar dois ou mais números:

1
2
3
4
5
6
7
8
9
10
11
12
(source +)
; (defn +
;   "Returns the sum of nums. (+) returns 0. Does not auto-promote
;   longs, will throw on overflow. See also: +'"
;   {:inline (nary-inline 'add 'unchecked_add)
;    :inline-arities >1?
;    :added "1.2"}
;   ([] 0)
;   ([x] (cast Number x))
;   ([x y] (. clojure.lang.Numbers (add x y)))
;   ([x y & more]
;      (reduce1 + (+ x y) more)))

Note que temos acesso a todos os detalhes internos da função +, incluindo sua documentação e mais algumas informações que são úteis para o compilador ou para alguma função que gere documentação automaticamente.

Ao tentarmos ver o código fonte de uma forma especial ou de algum código escrito nativamente em Java, receberemos uma mensagem de que o código fonte não foi encontrado:

1
2
(source Thread/sleep)
; Source not found

Há muito mais recursos no REPL do Clojure, inclusive no que diz respeito a integração com o seu editor preferido, mas vou deixar isso para outro artigo.

Sowing the Seeds of Code

| | Comments


Seguindo a filosofia de tirar a bunda da cadeira, tive a oportunidade de falar em excelentes eventos esse ano, conhecer muita gente interessante e dar os primeiros passos no sentido de disseminar a linguagem Clojure no Brasil e organizar comunidades em São Paulo e no Rio de Janeiro.

Coding Dojo

Em Maio tive a honra de ter sido convidado a apresentar um coding dojo de Clojure na Das Dad, que acabou tendo uma aceitação bem maior do que eu esperava.

Tirando o fato do trânsito de São Paulo ter me derrubado e me feito chegar atrasado, conseguimos dar uma boa olhada na linguagem e atiçar a curiosidade. Quem quiser dar uma olhada no que foi feito, o código está no GitHub.

Anhanguera

Ainda em Maio, Alexandre Borba e eu fomos convidados a palestrar na unidade de Osasco da Anhanguera Educacional. Borba apresentou os conceitos de Coding Dojo, Katas e o formato Randori para os alunos, enquanto eu apresentei conceitos de orientação a objetos com JavaScript.

Os organizadores do evento prepararam um espaço para os palestrantes atrás do palco que era muito melhor do que qualquer camarim que eu já tenha visto nas vezes em que toquei por aí.

Alguns alunos perguntaram sobre o uso de JavaScript no mercado de trabalho, o que me deu a certeza de que uma palestra menos técnica e mais focada na realidade deles teria sido melhor aproveitada.

2º ENCATEC

O pessoal da Adapti, empresa júnior encubada no CEUNES-UFES, em São Mateus-ES, me convidou para o Segundo Encontro Norte-Capixaba de Tecnologia.

Lá tive o prazer de assistir a palestra da Professora Mariella Berger, do projeto IARA, que apresentou o “famoso carro que atropelou a Ana Maria Braga” e o projeto da Universidade Federal do Espírito Santo que junta automatização, robótica, inteligência artificial, sistema de visão e mais um monte de coisas bacanas usando soluções de código aberto.

Tive a oportunidade também de dividir espaço com meus ídolos Álvaro Justen, o Turicas, que falou sobre software livre; Ricardo Valeriano, que fez uma apresentação hardcore sobre Paralelismo e Concorrência com Ruby; e Luiz Rocha apresentando conceitos de sistemas distribuídos.

Eu falei sobre as aplicações de programação funcional em ambientes corporativos, visto que os alunos da UFES “aprendem” programação funcional e Haskell logo no primeiro semestre de uma maneira bem deficiente.

Assim como aconteceu na Anhanguera, saí com a impressão de que um tema menos técnico teria sido melhor aproveitado pelos alunos. O fato dos alunos serem pagos com horas de atividades extra-curriculares também fez com que muitos estivessem ali obrigados, estando pouco interessados nos temas apresentados.

TDC SP

Em Julho tivemos a etapa de São Paulo do The Developers Conference, evento itinerante organizado pela GlobalCode.

Eu tive a oportunidade de apresentar a primeira palestra da trilha de JavaScript e HTML5, na Quarta-feira, e apresentei vários recursos menos conhecidos do JavaScript, além de ter sorteado uma cópia do meu livro.

Como eu utilizei o manjadíssimo recurso de usar animais para explicar herança, um dos presentes comentou depois, no Twitter, que acha idiota a analogia com animais, visto que ele nunca utilizou animais nos sistemas que já ajudou a desenvolver. Eu achei que seria desnecessário dizer, mas o problema aí mora no fato do colega não perceber que eu estava explicando um conceito de uma forma que qualquer pessoa possa entender. Uma vez que você entenda o conceito, você pode extrapolá-lo para a sua realidade, seja utilizando herança para definir Pessoas Físicas e Jurídicas, seja para definir classes fiscais e naturezas de operação da forma que for mais conveniente.

O ponto negativo ficou por conta da organização, que atrasou o início das palestras em pouco mais de uma hora, não apresentou justificativas nem um pedido de desculpas aos presentes.

No Domingo, último dia do evento, fui mentor de Clojure no Coding Dojo que aconteceu na trilha de Ruby. Ao mesmo tempo e na mesma sala havia máquinas com Clojure, Scala e Ruby para resolver o mesmo problema. Os participantes saiam de uma máquina e iam para outra, onde podiam ver as diferenças e características de cada uma das linguagens.

Fiquei feliz ao perceber que o Clojure causou uma boa impressão e, novamente, despertou o interesse.

QCon SP

O grande evento do ano para desenvolvedores, infelizmente, caiu nos mesmos dias do RubyConf, evento da Locaweb voltado para a comunidade Ruby e Rails. Ouvi coisas muito boas sobre o RubyConf desse ano, e foi uma pena que eu não estive por lá.

Como acontece todos os anos, o nível das palestras foi muito bom e, pela primeira vez, tive a honra de ter sido convidado para falar sobre Clojure na trilha “Fronteiras do Desenvolvimento”.

Para minha surpresa, a sala ficou lotada. Muita gente em pé, muita gente interessada e várias pessoas vieram falar comigo após a palestra para tirar dúvidas ou mesmo para contar suas experiências ao adotar Clojure em suas empresas.

Não tenho dúvidas de que, profissionalmente falando, esse foi o ponto alto do ano para mim. Só tenho a agradecer aos presentes e à organização pelo interesse e pela oportunidade.

O ponto negativo, e até certo ponto divertido, ficou por conta da novelinha criada por um dos palestrantes ao falar mal de SOA. Os representantes de um dos patrocinadores, que vendem consultoria no assunto, ao se sentirem ofendidos, iniciaram um festival de resmungos passivo-agressivos como se um bom diálogo fosse construído dessa forma. O evento em si e a organização nada tiveram a ver com isso, mas foi interessante acompanhar algumas reações e ver quem as pessoas realmente são quando o calo dói.

(clj-sp)

Entre um evento e outro, percebendo o interesse crescente na linguagem Clojure, resolvi chamar os desenvolvedores das listas Clojure Brasil e Clojure BR para participar do (clj-sp), o Grupo de Usuários Clojure no Brasil. Somos o primeiro grupo do tipo no Brasil e, por mais estranho que pareça, nosso primeiro encontro foi no Rio de Janeiro.

Organizado pelo nosso amigo Giuliani, sete desenvolvedores se reuniram num bar da Lapa, o equivalente carioca da Vila Madalena e trocamos experiências sobre o uso de programação funcional em geral e Clojure em particular em nossos respectivos trabalhos.

Dois dias depois tivemos o primeiro encontro em São Paulo, nas dependências da iMasters, contando com uma apresentação minha sobre a sintaxe do Clojure, Konrad Scorciapino falando sobre Datalog e Jonas Fagundes compartilhando sua experiência com Clojure em startups.

O auditório da iMasters ficou pequeno para tanta gente interessada e eu fiquei impressionado como apareceu muito mais gente do que eu estava esperando.

Dia 26 de Setembro, última Quinta-feira do mês, teremos o segundo encontro. Caso você tenha interesse, as informações estão em [http://www.meetup.com/clj-sp/].

O que vem por aí

  • 7masters JavaScript Já 25 de Setembro será dia de 7masters de JavaScript na iMasters. Eu e mais seis especialistas vamos apresentar lightining talks de sete minutos sobre algum assunto bacana relacionado à linguagem. Acesse [setemasters.imasters.com.br/edicoes/javascript/] e compareça.

  • DevDay Em outubro vou falar novamente sobre JavaScript no DevDay, um evento muito bacana voltado para desenvolvedores de software que vai acontecer em Belo Horizonte.

Vai ser uma chance bacana de conhecer vários desenvolvedores que eu admiro e aprender com quem realmente conhece do assunto.

  • Programação funcional E, finalizando, estou escrevendo meu segundo livro, mais focado em programação funcional e como isso pode salvar a sua pele no seu trabalho ou nos seus projetos pessoais. É um livro bem mais denso e especializado que o primeiro, mas estou tentando manter o mesmo tom didático e fácil de assimilar.

Esse ano está sendo o ano da colheita de tudo o que eu plantei em 2012 e, enquanto isso, estou me preparando para colher mais frutos no ano que vem. Vamos ver no que dá.

[Off-Topic] Transforme Seu Roteador Wireless Em Um NAS, MediaServer UPnP/DLNA E BitTorrent Client Com OpenWRT

| | Comments


TL;DR OpenWRT é uma distro Linux embarcável em roteadores que permite customizar e instalar serviços sem a necessidade de compilar um novo firmware. Em outras palavras, é um “firmware on steroids” para roteadores.

Mas por quê?

  • Economia. Apenas substituindo o firmware do roteador é possível adicionar funcionalidade e recursos, economizando uma grana preciosa com dispositivos semelhantes, como AppleTV, NAS dedicado, etc.
  • Funcionalidade. Provavelmente o roteador fica ligado 100% do tempo na sua casa, sendo utilizado apenas para compartilhar a internet. Praticamente um servidor disponível 24 horas por dia sem utilizaçao! Não mais…
  • Diversão. Substituir o firmware do seu roteador por uma distro baseada em Linux e configurar todos os serviços manualmente… é diversão pura!
  • Por que eu quero (e posso)!

Instalação

Atenção: existe a possibilidade (embora pequena) de algo sair muito errado e você ganhar um peso de papel. Prossiga por sua conta e risco… ou se tiver coragem!

O primeiro passo é descobrir o modelo do seu roteador e verificar a compatibilidade nesta página: http://wiki.openwrt.org/toh/start. Se não encontrar o modelo ou houver um aviso de não-compatibilidade, sinto muito mas não vai rolar para você.

Se seu roteador for compatível, haverá o target da imagem e um link para uma wiki com um how-to específico do modelo, instruções de instalação, resolução de problemas, etc. Procure pelo nome da release que você deverá baixar daqui http://downloads.openwrt.org. A imagem tem o formato openwrt-TARGET-generic-MODELO-VERSAO-squashfs-factory.bin

Por exemplo, meu roteador é um TP-Link WR1043ND. Procurando na página http://wiki.openwrt.org/toh/start, o target compatível com meu roteador é ar71xx:

Acessei a página de downloads, naveguei pelos diretórios da release, depois target e encontrei o arquivo openwrt-ar71xx-generic-tl-wr1043nd-v1-squashfs-factory.bin para download.

Quando baixar a imagem, é só fazer a atualização de firmware normalmente no seu roteador pelo admin:

Confirme e reze muito! Aguarde o roteador atualizar e reiniciar.

Deste ponto em diante, será necessário conectar no roteador com cabo, para setar uma senha de root e configurar o wifi. Este processo é relativamente trivial, basta utilizar a interface web do admin, sem segredo…

Uma vez definida a senha e o wifi configurado, é possível acessar seu roteador via ssh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ssh root@192.168.1.1
root@192.168.1.1's password:

BusyBox v1.19.4 (2013-03-14 11:28:31 UTC) built-in shell (ash)
Enter 'help' for a list of built-in commands.

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 ATTITUDE ADJUSTMENT (12.09, r36088)
 -----------------------------------------------------
  * 1/4 oz Vodka      Pour all ingredients into mixing
  * 1/4 oz Gin        tin with ice, strain into glass.
  * 1/4 oz Amaretto
  * 1/4 oz Triple sec
  * 1/4 oz Peach schnapps
  * 1/4 oz Sour mix
  * 1 splash Cranberry juice
 -----------------------------------------------------
root@OpenWrt:~#

Se você chegou até aqui, parabéns! Você teve coragem. E felizmente a parte difícil já passou…

Nota: dependendo do seu roteador, a versão do OpenWRT pode variar. Leia a wiki do modelo do seu roteador para instruções específicas e resolução de problemas.

Importante: caso o pior aconteça (como acabar a energia no meio do processo de flash da firmware) e você não queira utilizar seu roteador como peso de papel, tente seguir os procedimento de “debriking” em http://wiki.openwrt.org/doc/howto/generic.debrick

Configurando o NAS

Para o NAS, vamos montar as partições do HD externo e configurar uma partição de swap pois o roteador provavelmente não tem memória interna suficiente para dar conta do recado.

Neste exemplo, vou usar meu HD de 1TB com uma partição formatada em ext4 e outra partição de 1GB formatada como linux+swap. Não vou usar FAT32 nem NTFS e nem recomendo! A idéia aqui é deixar o HD plugado eternamente no roteador e acessá-lo pela rede.

Instalando os pacotes necessários

Para nossa sorte, o OpenWRT conta com um gerenciador de pacotes que facilita (e muito) a instalação das libs e módulos necessários para montar o HD externo e compartilhá-lo na rede via Samba ou NFS.

Logue no roteador via ssh e instale os seguintes pacotes:

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install kmod-usb-storage block-mount kmod-fs-ext4

O ultimo pacote vai depender do sistema de arquivos do seu HD. Por exemplo, kmod-fs-ext2, etc. Veja os módulos disponíveis em http://wiki.openwrt.org/doc/howto/storage

Verifique se as partições já foram detectadas rodando blkid:

1
2
3
root@OpenWrt:~# blkid
/dev/mtdblock2: TYPE="squashfs"
/dev/sda1: LABEL="MEDIA" UUID="1ecad5f1-0000-0000-000f-e8b7f6ac651d" TYPE="ext4"

Montando as partições do HD

Agora vamos configurar o fstab para montar as partições automaticamente quando o roteador for ligado. Edite o arquivo /etc/config/fstab com o seguinte (use o vi):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
config global automount
        option from_fstab 1
        option anon_mount 0

config global autoswap
        option from_fstab 1
        option anon_swap 0

config mount
        option target   /mnt/media
        option device   /dev/sda1
        option fstype   ext4
        option options  rw,sync
        option enabled  1
        option enabled_fsck 0

config swap
        option device   /dev/sda2
        option enabled  1

No meu caso, vou montar a partição /dev/sda1 em /mnt/media, então é necessário criar o diretório de destino, ativar e iniciar o serviço fstab:

1
2
3
root@OpenWrt:~# mkdir /mnt/media
root@OpenWrt:~# /etc/init.d/fstab enable
root@OpenWrt:~# /etc/init.d/fstab start

Pronto, seu HD externo pode ser acessado em /mnt/media e o swap foi montado na segunda partição.

Compartilhando na rede

Vou utilizar NFS para compartilhar a partição na rede, mas você também pode utilizar Samba seguindo http://wiki.openwrt.org/doc/howto/cifs.server

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install nfs-kernel-server

Edite (ou crie) o arquivo /etc/exports com o conteúdo:

1
/mnt/media 192.168.1.0/255.255.255.0(rw,sync,no_subtree_check)

Ative e inicie os serviços necessários:

1
2
3
4
root@OpenWrt:~# /etc/init.d/portmap start
root@OpenWrt:~# /etc/init.d/portmap enable
root@OpenWrt:~# /etc/init.d/nfsd start
root@OpenWrt:~# /etc/init.d/nfsd enable

Pronto! O server (roteador) está configurado. Para montar esta partição no client, por exemplo Ubuntu, siga:

1
2
3
$ sudo apt-get install nfs-common
$ sudo mkdir /media/nas
$ sudo mount -t nfs 192.168.1.1:/mnt/media /media/nas

Groovy! Seu NAS foi montado no seu desktop em /media/nas. Pode compartilhar seus arquivos a vontade!

Configurando o MediaServer (minidlna)

Logue no roteador e instale os pacotes necessários:

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install minidlna

Para configurar, basta editar o arquivo /etc/config/minidlna como segue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
config minidlna config
  option 'enabled' '1'
  option port '8200'
  option interface 'br-lan'
  option friendly_name 'OpenWrt DLNA Server'
  option db_dir '/mnt/media/minidlna/db'
  option log_dir '/mnt/media/minidlna/log'
  option inotify '1'
  option enable_tivo '0'
  option strict_dlna '0'
  option presentation_url ''
  option notify_interval '900'
  option serial '12345678'
  option model_number '1'
  option root_container '.'
  list media_dir '/mnt/media'
  option album_art_names 'Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg'

Ative e inicie o serviço:

1
2
root@OpenWrt:~# /etc/init.d/minidlna enable
root@OpenWrt:~# /etc/init.d/minidlna start

Pronto! O MediaServer está configurado e compartilhando seus arquivos via DLNA na rede. Aqui na minha SmartTV aparece assim:

MiniDLNA na Samsung SmartTV

Configurando o BitTorrent client (transmission)

Vamos instalar os pacotes:

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install transmission-daemon

E configurar o serviço editando /etc/config/transmission e alterando as opções a seguir (mantenha todas as outras configs):

1
2
3
4
5
6
7
8
9
10
11
12
13
config transmission
  option enabled 1
  option config_dir '/mnt/media/transmission/config'
  option download_dir '/mnt/media/transmission/complete'
  option incomplete_dir '/mnt/media/transmission/incomplete'
  option incomplete_dir_enabled true
  option ratio_limit 2.0000
  option ratio_limit_enabled true
  option rpc_authentication_required false
  option rpc_password ''
  option rpc_username ''
  option speed_limit_up 5
  option speed_limit_up_enabled true

Ative e inicie o serviço:

1
2
root@OpenWrt:~# /etc/init.d/transmission enable
root@OpenWrt:~# /etc/init.d/transmission start

O Transmission roda como um daemon, sendo controlado remotamente. Para isso, será necessário adicionar a seguintes regra de farewall, adicionando no final do arquivo /etc/config/firewall:

1
2
3
4
5
config rule
        option src *
        option proto tcp
        option dest_port 9091
        option target ACCEPT

Reinicie o firewall executando /etc/init.d/firewall restart

Para gerenciar seus torrents, instale a extensão .torrent to Transmission no Chrome, baixe o Transmission Remote GUI para Windows, Mac e Linux ou ainda o Remote Transmission para Android.

Transmission GUI

Caso seu roteador tenha espaço disponível, você pode instalar o pacote transmission-web para gerenciar seus torrents diretamente do navegador.

Ao adicionar um .torrent pelo GUI, o mesmo será baixado diretamente no seu roteador!

Conclusão

Embora relativamente complicado, todo procedimento para instalação e configuração não é nada diferente do que estamos acostumados a fazer diariamente no Linux, como devops, seja configurando uma VPS ou montando um NAS na rede.

A liberdade que o OpenWRT oferece é imensa. O repositório é recheado com pacotes bem úteis e muito fáceis de configurar. Sem falar na economia comparando com um aparelho como AppleTV (que não ofecererá tantos recursos) ou outros media servers do mercado.

E aí, tem coragem??? Poste sua experiência nos comentários! :)

Referências