O mesmo aplicativo 4 vezes: PHP vs Python vs Ruby vs Clojure

PorAdam Bard em

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="/">&lt;&lt; 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.

  1. O arquivo nouns.txt é necessariamente lido e separado a cada requisição, o que não é opcional.
  2. 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/

Mensagem do anunciante:

Ter a sua própria empresa de hospedagem nunca foi tão simples. Clique aqui.

Deixe um comentário! 37

37 comentários

Comentários

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Comentando como Anônimo

  1. Eu sinceramente achei lamentável os exemplos de codificação.
    Em PHP você não usou PEAR nem Composer, usou a linguagem o mas pura possível, ok. Só que depois vocẽ diz que no ruby é diferente pq vc colocou as dependências no gemfile (poderia ter feito isso tbm no composer.json).
    Depois você diz que no ruby e python o código é mais legivel, esta melhor organizada (é claro com python e ruby vc separou seus arquivos e usou frameworks em PHP poderia ter utlizado Silex, Slim, Respect e Twig para templates mas não o fez).
    Ou seja pra ruby e python jogou um monte de purpurina no código pra ficar “arejado” e em PHP deixou cru nem testou e falou que a linguagem é inconsistente,
    ai não da né amigo…

    1. E mesmo o PHP sem essa purpurina toda, foi o que teve menos linhas e foi o mais claro de se entender…. apesar de gostar bastante de python mas.. php faz bem as coisas que se propõe a fazer… excelente comentário Marcus Sá.

    2. Obervação perfeita Marcus Sá, parabéns!
      PHP só é inconsciente para quem o programa de forma inconsciente.

      Na maioria das vezes quem reclama do PHP não sabe programar na “raça”, precisa de Visual Studio, NetBeans, ou qualquer outra ferramenta. Isso é o que mais vejo aqui na minha cidade.

      1. Eu utilizo o próprio ‘ nano ‘ do terminal Linux e Emacs, e o Gedit.
        É que não gosto de IDE kkk.
        E algumas vezes também reclamo de PHP.
        Não existe linguagem melhor.
        Cada linguagem mostra suas ideias de como resolver o problema.
        Quanto a legibilidade. Sinceramente acho mais fácil em Python. Porém cada pessoa é diferente, a pessoa que entende muito de Php não sente nenhuma dificuldade.
        E outra que estamos falando de linguagens de alto nível.

      2. O autor do texto pode até ter sido meio parcial, mas isso não quer dizer que PHP seja uma boa linguagem de programação.

        Joga no Google “PHP – A Fractal of Bad Design”. Nem precisa ler tudo, é só ler a analogia no começo.

    3. Ah, e mais, não dá pra você pegar e rodar Python ou Ruby na web e pronto. Você PRECISA de um framework pra isso. Então, para aplicações na Web, isso É, de fato, a linguagem na forma mais pura possível.

    4. “Ah, e mais, não dá pra você pegar e rodar Python ou Ruby na web e pronto. Você PRECISA de um framework pra isso. Então, para aplicações na Web, isso É, de fato, a linguagem na forma mais pura possível”

      Rodar servidor em Ruby:
      ruby -run -e httpd — -p 5000 .
      Rodar servidor em Python:
      python -m SimpleHTTPServer

      Sem framework. Seu argumento é inválido cara.

    5. Também ahei muito fraco o artigo marcus, eu construo minhas aplicações em node, mas simpatizo muito com PHP, e vi que o autor não mencionou o composer, e se quer pesquisou frameworks, falou besteira sobre inconsistência na linguagem, nunca vi isso. Alem disso PHP Possui Comunidade imensamente superior as demais linguagens, e que nunca vi esnobistas, se quer realmente citar vantagem ou desvantagem de algum linguagem sobre outra, vamos falar de benchmark. Faça um traceroute da requisição até o método de seu controller em cada linguagem, e vejamos qual tem mais overhead, vejamos também o teste de requisição por segundo.. são muitos porém’s ao se analisar que linguagem tem vantagem sobre a outra, e não apenas trazer um exemplo eespecífico e da forma que foi abordada, concluir qual a melhor. O que acho válido seria informar oque da pra fazer mais facilmente em uma linguagem, que outra. Isso sim me traria informação de valor. Por exemplo trabalhar com sockets, qual linguagem facilita minha vida? Eu particularmente prefiro node. Então, para os que leram o artigo e não ficaram satisfeitos, sugiro que busque a linguagem ideal a partir do tipo de aplicação que você deseja construir.

  2. Achei o artigo muito tendencioso.
    E tem que ser muito amador para não saber a diferença entre str_replace e strtoupper, elas são totalmente diferentes.

    str_replace substitui trechos de uma string.
    strtoupper converte os caracteres de uma string para maiúsculos.

    Onde está a inconsistência ? Lamentável

  3. Sincereamente esse cara não entende de PHP, e concordo com Marcus. Existe muito programadorzinho de merda que acha que PHP não é organizado.

  4. Gostei de todos os comentários postados acima mas concordo com o autor quando ele se refere a padronização de algumas funções do PHP “str_replace ou strtoupper?”. Ele falhou ao tentar fazer uma comparação entre linguagens pesos e medidas diferentes.

  5. A minha indignação não eh kra falar isso, minha indignação eh, cara não ter conceito sobre a linguagem e somente fazer um CTRL+C e CTRL+V.

    irmão, pesquisa mais e faz um prova de conceito melhor!!!

  6. Primeiramente deve-se entender bem de uma linguagem para qualificá-la ou desqualificá-la, o que nos deixa a entender que o autor do codigo acima privilegiou somente às que lhe conferia mais conhecimento.

  7. Não minha opinião não devemos fazer nenhum tipo de julgamento sobre as linguagens de programação, dependendo do modo que você for utilizar é o que vai contar no final, uma linguagem poder sem melhor do que a outra dependendo da necessidade.

  8. Discussões à parte, achei interessante a comparação entre linguagens. Abstraindo gostos e opniões sobre as linguagens, deu para ter uma idéia de como cada linguagem pode resolver um determinado problema. Legal!!!

  9. Sinceramente, uma perda de tempo ler este artigo. Um autor sem conhecimento profundo em algumas e talvez todas linguagens citadas e querendo desmerecer o PHP. Por favor…

  10. o mercado pouco se importa com qual linguagem estão sendo escrito os programas, se importam somente com a produtividade, preço, performance, qualidade e portabilidade… para de escrever essas porcarias tendenciosas e vá ocupar seu estudando mais para escrever artigos relevantes para a área de ti, se vc programa nas quatro linguagens vc não possui profundidade suficiente em nenhuma delas ou somente em 1 ou 2, portanto não tem argumentos para fazer esse tipo de comparação, que como disse, pouco importa para o mercado. Linguagem de programação é igual c*, cada um tem o seu.

  11. Quanto ao que o Marcus Sá disse é verdade. Achei meio injusto também.
    Pois o Php também possui grandes maneira de programar que deixam o código muito legivel.
    Não é muito sensato comparar um framework de uma linguagem com uma linguagem.

  12. Antes da versão 5.3 o PHP não tinha namespace, antes da versão 5 a orientação a objetos era péssima, o PHP evolui muito (agora tem namespace, trait, etc) mas acredito que os programadores também tem que evoluir.

  13. Sem comentários…acho que para criticar qualquer coisa você deve, ao menos, conhecer a fundo sobre o que você está falando. Esses programadores de “modinha” são complicados.

    Concordo com o Matheus Oliveira: “PHP só é inconsciente para quem o programa de forma inconsciente”. A questão é que o PHP é “mais liberal” em relação a forma de programar e não existe uma “babá” te obrigando a seguir certas regras.

    Se você estudou, conhece a linguagem, sabe sobre Design Patterns, Orientação a objetos e dos frameworks disponíveis, fará as coisas muito bem com PHP. Para mim, isso é desculpa de pessoas sem conhecimento da linguagem.

leia mais
Este projeto é mantido e patrocinado pelas empresas: