Aqui está um programa de brincadeira que eu escrevi em PHP, Python, Ruby e Clojure. Espero que seja útil para alguém que sabe pelo menos uma dessas linguagens e quer aprender outra.
Esse programa é chamado de “Nurblizador” e faz apenas uma coisa: aceita qualquer texto e tenta substituir qualquer palavra que não seja um substantivo pela palavra “nurble”. Ele já está funcionando e rodando em http://nurblizer.herokuapp.com/.
Primeiro em PHP. O repositório pode ser encontrado em https://github.com/adambard/nurblizer-php.
Não sou um grande fã de PHP, mas não dá para discutir sobre a simplicidade de seu deployment. O repositório possui apenas seis arquivos.
O arquivo index.php contém apenas HTML porque, na verdade, ele não faz coisa alguma.
<?php include '_header.php'; ?>
<h1>Nurblizer</h1>
<form action="/nurble.php" method="post">
<fieldset>
<ul>
<li>
<label>Text to nurblize</label>
<textarea name="text"></textarea>
</li>
<li>
<input type="submit" value="Nurblize Away!">
</li>
</ul>
</fieldset>
</form>
<p>
<a href="http://www.smbc-comics.com/?id=2779">wtf?</a>
</p>
<?php include '_footer.php';
A mágica acontece no nurble.php
<?php
$nouns = file("nouns.txt", FILE_IGNORE_NEW_LINES);
function nurble($text){
$text = strtoupper($text);
$words = preg_split(
'/\w/',
preg_replace('/[^a-z ]/', '', strtolower($text)));
foreach($words as $word){
if(!in_array($word, $nouns)){
$pattern = '/(\b)' . $word . '(\b)/i';
$replacement = '\1<span class="nurble">nurble</span>\2';
$text = preg_replace($pattern, $replacement, $text);
}
}
return str_replace("\n", '<br>', $text);
}
include '_header.php'; ?>
<h1>Your Nurbled Text</h1>
<div><?php echo nurble($_POST['text']); ?></div>
<p>
<a href="/"><< Back</a>
</p>
<?php include '_footer.php';
O que acontece aqui?
- O arquivo nouns.txt é lido para produzir um array de substantivos.
- O texto é convertido para maiúsculo.
- A lista de palavras no texto é produzida ao converter as palavras para letra minúscula e depois separadas utilizando preg_split
- É feito um loop pela lista e, se uma palavra estiver na lista de substantivos, cada instância daquela palavra no texto é substituída por um trecho de código HTML que mostra a palavra “nurble”.
Ok, isso parece funcionar (aviso: eu não quis instalar o Apache+PHP na minha máquina de desenvolvimento, portanto, não testei essa versão). Deployment em PHP funciona assim:
- certifique-se de que o seu servidor está executando o apache e mod_php
- ponha os arquivos onde o servidor espera encontrá-los
Então por que não paramos por aqui? Bem, há algumas objeções para serem feitas em relação ao código acima.
- O arquivo nouns.txt é necessariamente lido e separado a cada requisição, o que não é opcional.
- A linguagem é inconsistente em alguns lugares. É str_replace ou strtoupper?
Em seguida, queremos tentar em Ruby. Diferentemente do PHP, não é possível simplesmente jogar código em Ruby no servidor e esperar que ele funcione; você provavelmente irá desejar utilizar um framework para fazer a maior parte do trabalho para você.
Aqui entra o Sinatra. O Sinatra é um microframework em Ruby que faz um ótimo trabalho ao conservar as coisas simples. Você pode encontrar os exemplos do nurblizador aqui: https://github.com/adambard/Nurblizer
Como no Rails, seu irmão maior, o Sinatra não se importa em fazer algumas suposições ele mesmo. Ele entende que queremos nossos templates em views/ e nossos arquivos estáticos em public/
Chega. Aqui está o código:
require 'sinatra'
# configure block is a Sinatra feature
configure do
@@nouns = File.open('nouns.txt').map{|line|
line.strip.downcase
}
end
def nurble(text)
text = text.upcase
words = text.downcase().gsub(/[^a-z ]/, '').split
words.each{|w|
if not @@nouns.include? w
pattern = Regexp.new('(\b)'+ w + '(\b)', Regexp::IGNORECASE)
replacement = "\1<span class=\"nurble\">nurble</span>\2"
text.gsub! pattern, replacement
end
}
text.gsub(/\n/, '<br>')
end
get "/" do
haml :index
end
post "/nurble" do
haml :nurble, :locals => {
:text => nurble(params["text"])
}
end
Então o que é diferente?
- Agora carregamos a variável @@nouns apenas uma vez, no bloco de configuração, e podemos nos referir a ela a cada requisição.
- Nossos templates estão resguardados no diretório views/ e ainda podemos utilizar HAML, o que é muito legal.
- Nossas dependências podem ser declaradas no Gemfile do projeto, que pode ser facilmente instalado usando bundle install.
- No geral, o código é bem mais “arejado”, não acham?
O pedaço relacionado às dependências é o mais interessante para mim. Em PHP, o problema das dependências fica a cargo do Apache e, por extensão, do servidor. O PEARS ajuda bastante, mas mesmo ele pode não estar disponível sempre. Em nosso projeto em Ruby, todas as dependências estão como de costume disponíveis para revisão e instaláveis via rubygems.
Suponha que iremos passar para Python agora. Talvez tenhamos restrições de desempenho e podemos imaginar que em Python seria mais rápido (me disseram que isso não é mais verdade no Ruby 2.0, portanto, coloque o pretexto que preferir acima).
Em Python, o melhor microframework é o Flask, até onde eu sei. Há muitos outros, mas eles não possuem implementações de nurblizadores feitas com ele. Aqui está o código que utilizaremos: https://github.com/adambard/py-nurblizer
O Flask coloca os templates em templates/ e arquivos estáticos em static/. No resto, é igual ao Sinatra (mas em Python).
import re
from flask import Flask, render_template, request
app = Flask(__name__)
# Read in the nouns file
with open("nouns.txt") as f:
NOUNS = [l.strip().lower() for l in f]
# Nurblize!
def nurble(text):
text = text.upper()
words = re.sub(r'[^a-z ]', '', text.lower()).split()
for word in words:
if word not in NOUNS:
pattern = r'(\b)' + word + r'(\b)'
replacement = r'\1<span class="nurble">nurble</span>\2'
text = re.sub(pattern, replacement, text, flags=re.I)
return text.replace('\n', '<br>')
@app.route("/", methods=['GET'])
def index_view():
return render_template("index.html")
@app.route("/nurble", methods=['POST'])
def nurble_view():
return render_template(
"nurble.html",
text=nurble(request.form.get('text', '')))
if __name__ == "__main__":
app.run()
Note que a abordagem do Flask é ser uma linguagem menos específica para aplicativos web do que é o Sinatra. Em vez de blocos get e post, temos decoração de roteamento em antigas funções do Python. Note que, no Flask, definimos um aplicativo como objeto, acrescentamos as rotas a ele e o executamos explicitamente no final do arquivo.
Ok, então agora o nosso aplicativo nurblizador está ficando realmente popular e precisamos portá-lo para o Clojure. Por quê? Porque eu já escrevi um exemplo, droga! Aqui está: https://github.com/adambard/nurblizer-clj
Usar o Clojure para esse aplicativo é como utilizar um canhão para matar uma mosca, mas ele tem algumas propriedades bem legais. Ele trata todos os dados como imutáveis, a não ser que você faça de outra forma, o que é ótimo para paralelismo. A linguagem roda na JVM, que é amplamente disponível e é muito rápida também.
Escrever aplicativos web em Clojure é um pouco diferente dos outros microframeworks em outras linguagens. O Noir, um framework Clojure, não está mais sendo mantido, contudo não escreveremos muito mais código se acrescentarmos nosso próprio microframework a partir dos componentes disponíveis. Então faremos isso!
(ns nurblizer.core
(:gen-class :name nurblizer.core)
(:use compojure.core nurblizer.helpers)
(:require
[clojure.string :as str]
[ring.adapter.jetty :as ring]
[compojure.core :as compojure]
[compojure.route :as route]
[compojure.handler :as handler]))
; Read in the nouns file on startup
(def nouns
(map (comp str/trim str/lower-case)
(-> (slurp (clojure.java.io/resource "nouns.txt"))
(str/split #"\n"))))
; Nurblize function: now with recursion!
(defn nurble
([text]
; First run: prepare the wordlist and upper-case the text.
(let [words (-> text
str/lower-case
(str/replace #"[^a-z ]" "")
(str/split #"\s"))]
(nurble (str/upper-case text) words)))
([text words]
; Recursively update <text> by replacing each <word> of <words> iff <word> is in nouns
(if (not (empty? words))
(let [w (first words)
pattern (re-pattern (str "(?i)(\\b)" w "(\\b)"))
replacement "$1<span class=\"nurble\">nurble</span>$2"
text (if (not (some (partial = w) nouns))
(str/replace text pattern replacement)
text)]
(recur text (rest words)))
(str/replace text #"\n" "<br>"))))
; Define handlers
(defn index-view []
(render "index" {}))
(defn nurble-view [text]
(render "nurble" {:text (nurble text)}))
; Routes
(defroutes main-routes
(GET "/" [] (index-view))
(POST "/nurble" [text] (nurble-view text))
(route/resources "/static"))
; And finally, the server itself
(defn -main []
(ring/run-jetty (handler/site main-routes) {:port 9000}))
A primeira coisa que você irá notar é que importaremos muito mais bibliotecas do que nas outras linguagens. Estaremos utilizando tudo que está disponível em compjure.core e nurblizer.helpers (o código fonte disso está logo abaixo). Usamos o Ring e o adaptador Jetty para executar o servidor, o Compojure para manipular o roteamento e para conectá-lo ao Ring. Também utilizaremos o Clostache, uma implementação do Mustache, para renderizar os templates; essa é a função de renderização no final de cada view. Usaremos também o Leiningen como gerenciador do projeto/solucionador de dependências.
A função nurblizer é bem mais imponente aqui. Não queremos colocar o texto no lugar das palavras originais da mesma forma como fizemos com os outros 3 exemplos, por causa da imutabilidade mencionada anteriormente. É claro que sempre podemos contar com os objetos e métodos em Java, mas nesse caso estaríamos escrevendo em Java. Em vez disso, iremos atualizar o texto recursivamente, uma palavra de cada vez.
Aqui está a função de renderização:
(ns nurblizer.helpers
(:require
[clostache.parser :as clostache]))
(defn read-template [template-file]
(slurp (clojure.java.io/resource (str "templates/" template-file ".mustache"))))
; Quick-and-dirty Mustache renderer.
(defn render
([template-file params]
(clostache/render (read-template template-file) params
{:_header (read-template "_header")
:_footer (read-template "_footer") })))
Mas por que Clojure? Bem, para início de conversa, seu desempenho deve ser o melhor de todos. Isso é duplamente verdade caso você utilize um servidor como o httpkit, que eu testei com até 100 conexões simultâneas em uma única instância do Heroku. Você pode empacotá-lo utilizando lein uberjar e ter um único arquivo, o qual pode ser executado em qualquer lugar que tenha Java.
Tudo isso faz com que compense utilizar Clojure para esse aplicativo bobo? Na minha opinião, claro que não. A versão em Ruby é muito mais fácil de ler e bastante poderosa. Mas utilizar Clojure logo de início pode economizar problemas caso seu projeto precise ser expandido no futuro. Também é muito divertido, mas essa é a minha opinião pessoal.
Este artigo gerou uma discussão de qualidade no Hacker News, então dê uma olhada por lá em: https://news.ycombinator.com/item?id=5440170
Quanto ao algoritmo do nurblizador: muitas melhorias foram propostas. A mais óbvia delas é utilizar um conjunto ou hash table para armazenar os substantivos. Eu encorajo qualquer um a mergulhar mais fundo e enviar pull requests em meus repositórios para o nurblizador.
Algumas pessoas também contribuíram com implementações em outras linguagens:
GO
JAVASCRIPT (NODE.JS)
JSP
greyrest, do Hacker News, fez esta versão em Clojure, que é a minha favorita até agora (e preserva os espaços para boot). Eu a coloco aqui, porque penso que meus esforços para espelhar as outras implementações na versão Clojure não representam a linguagem tão bem quanto ela merece.
(ns nurblizer.core
(:gen-class)
(:use compojure.core)
(:require
[clojure.string :as str]
[clostache.render :as clostache]
[ring.adapter.jetty :only run-jetty]
[compojure.handler :only site]))
;; main nurble stuff
(def nouns
(->> (-> (slurp (clojure.java.io/resource "nouns.txt")) ; read in nouns.txt
(str/split #"\n")) ; split by line
(map (comp str/trim str/upper-case)) ; feed the lines through upper-case and trim
set)) ; transform into a set
(def nurble-replacement-text "<span class=\"nurble\">nurble</span>")
(defn nurble-word [word]
(get nouns (str/upper-case word) nurble-replacement-text)) ; return word if word in set else nurble
(defn nurble [text]
(str/replace text #"\n|\w+" #(case % ; using anon func literal, switch on argument
"\n" "<br>" ; when arg is newline replace with br
(nurble-word %)))) ; otherwise nurble the argument (a word)
;; webserver stuff
(defn read-template [template-file]
(slurp (clojure.java.io/resource (str "templates/" template-file ".mustache"))))
(defn render
([template-file params]
(clostache/render (read-template template-file) params
{:_header (read-template "_header")
:_footer (read-template "_footer") })))
(defroutes main-routes
(GET "/" [] (render "index" {}))
(POST "/nurble" [text] (render "nurble" {:text (nurble text)})))
(defn -main []
(run-jetty (site main-routes) {:port 9000}))
***
Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://adambard.com/blog/PHP-ruby-python-clojure-webapps-by-example/



