
The same app 4 times: PHP vs Python vs Ruby vs Clojure - adambard
http://adambard.com/blog/PHP-ruby-python-clojure-webapps-by-example/
======
DigitalSea
Is it just me or is PHP easily the best choice for quickly implementing
something like this? The Ruby example required the framework Sinatra, Python
isn't a language most servers support out-of-the-box (it is beautiful though)
and Clojure is the ugliest language I've ever seen (at least in this example).
I look forward to the day PHP is standardised (it will happen), because it
cops a lot of flak when it gets the job done without needing usually anything
installed on the server to run it. Upload your scripts and run, BAM!

~~~
andyl
Yeah - just you. (jk :-)

Problem with PHP (for me) is that you gotta install Apache to make it work.

~~~
skore
Problem with PHP is he loaded the nouns list outside of the function. Not sure
whether he tested the code at all ;-)

~~~
adambard
Not the PHP code, no. I can only guarantee that it almost works.

~~~
skore
And I can guarantee that it's a lot of these

    
    
       PHP Notice:  Undefined variable: nouns in nurble.php on line 12
       PHP Notice:  Undefined variable: nouns in nurble.php on line 12
       PHP Notice:  Undefined variable: nouns in nurble.php on line 12
       PHP Notice:  Undefined variable: nouns in nurble.php on line 12
       PHP Notice:  Undefined variable: nouns in nurble.php on line 12
    

;-)

(Also: check out explode() instead of preg_split() and with a simple pattern
like this, str_ireplace might be faster, as might be searching for the word in
the whole text file instead of converting it into an array.)

------
Jemaclus
Interesting article, except that the examples don't line up exactly. For
example, in PHP it's trivial to store the file contents in a var and refer to
that over and over (as OP does in Ruby), but instead he chooses to read from
the file over and over again and then calls that a shortcoming of PHP vs Ruby
when that's purely an implementation decision.

Ruby is clean (but I dislike the stack), and Python looks good, but ye gads is
Closure ugly... Uglier than PHP, even.

My favorite part about PHP is how easy it is to get a simple Nurble-like app
up and running... but that's just me.

~~~
teraflop
OK, I'll bite: how would you store the array of words in memory across
requests in PHP? As far as I'm aware, the runtime deliberately doesn't provide
a way to do that.

The closest you could get is to store it in $_SESSION, but that's per-user
rather than global, and it still has to read the data from your session
handler. Which by default just uses flat files, so we're no better off than we
started.

~~~
jamesmoss
The APC opcode cache extension also has a user cache which has an API similar
to memcache but a bit faster since it's using shared memory and not going over
the network. It's shared between all visitors across all requests.

<http://php.net/manual/en/book.apc.php>

------
niggler
"full disclosure: I didn’t want to install Apache+PHP on my dev machine, so I
haven’t tested this one"

Something rubbed me wrong with this statement. If you are going to make a
remark like '[Clojure's] performance should be the highest of the above,'
shouldn't you at least try them first?

~~~
mscarborough
Yeah, that was kind of odd. Apache and PHP are probably the easiest stack to
install out of these four, so why bother including it in your article?

Full quote: "Ok, that seems to work (full disclosure: I didn’t want to install
Apache+PHP on my dev machine, so I haven’t tested this one). Deployment in PHP
works like this: Make sure your server is running apache and mod_php Put the
files where the server expects"

So which one is it, did you not install Apache+PHP or do you just check out
and put files where the server expects?

More proofreading would be nice.

~~~
vilepickle
I laughed at that too. Let's take the hard way out and test these admittedly-
ridiculous-to-deploy web stacks instead of spending a few minutes installing a
LAMP stack.

PHP can scale too: look at Facebook as an example or any hugely popular
website running Wordpress/Drupal. Where do you want so spend your time:
scaling your PHP production environment or configuring your
JVM/rvm/Python/Passenger?

------
cheald
The original SMBC specifies that everything _except_ the nouns should be
nurbled; your implementations replace the nouns themselves.

I'd like to refactor the Ruby example, but I'd like to know what the proper
implementation should be!

~~~
adambard
You are correct. I've updated the gists and text accordingly.

The Ruby example was already correct.

~~~
davesims
The inevitable ruby style niggling, feel free to ignore: personally I like
something a little more concise and inlined, some parts seem to have used more
verbose idioms, (like Regexp.new instead of a literal), and also I think you
need to escape the backreferences or use single quotes, something like:

    
    
      (words - @@nouns).each do |w|
          text.gsub!(/(\b)#{w}(\b)/i, '\1<span class="nurble">nurble</span>\2')
      end
    

EDIT: oops, backslash. Also, in the example, line 19, where you have 'sub',
don't you mean 'pattern'?

~~~
adambard
I submitted this article expecting to learn a few things about my style. In
this case, I didn't actually know you could interpolate into regexes like
that, so thanks!

And thanks for the typo catch, too.

~~~
davesims
No problem, in ruby style is always pretty subjective since there's a thousand
ways to do just about everything.

And, just FYI you _do_ need to escape the backrefs or use single quotes, like:

    
    
      "\\1<span class=\"nurble\">nurble</span>\\2"

~~~
davesims
I take it back - I had issues with escaping on irb but not in the app itself.
Something new to learn...

------
arocks
A trivial speed optimisation for the python example would be to use sets
instead of lists for e.g. change line 8 to:

    
    
        NOUNS = {l.strip().lower() for l in f}

------
ilaksh
I think it actually might be a little easier to read in Node.js/CoffeeScript.

    
    
        fs = require 'fs'
        express = require 'express'
        app = express()
    
        app.use express.bodyParser()
        app.use app.router
        app.use express.static(__dirname + '/public')
        app.set 'views', __dirname + '/views'
        app.set 'view engine', 'jade'
    
        nouns = fs.readFileSync('nouns.txt', 'utf8').split('\n')
    
        nurble = (text) ->
          text = text.toUpperCase()
          words = text.toLowerCase().replace(/[^a-z ]/g, '').split(' ')
    
          for word in words
            if word not in nouns
              re = new RegExp "(\\b)#{word}(\\b)","i"
              replacement = '$1<span class="nurble">nurble</span>$2'
              text = text.replace re, replacement
    
          text.replace  /\n/g, '<br>'
    
        app.get '/', (req, res) ->
          res.render 'index'
    
        app.post '/nurble', (req, res) ->
          res.render 'nurble', {nurble: nurble req.body.text}
    
        app.listen 3005
    

<https://github.com/ithkuil/nurble-coffeescript>

------
danneu
Cool stuff. The danger of this, though, is that you risk doing unidiomatic
examples a disservice.

I thought you were just going for line-for-line parity until I came across the
Clojure example. I think you could've written that same `for` loop in Clojure
and either yield the original word or yield "nurble" if it intersected a noun.
But this is put up or shut up territory and I'll take another look.

Also, could you clarify why you make multiple assertions that Clojure is "too
much" for this task?

I'm a fulltime Ruby developer learning Clojure on the side and Clojure's
Ring+Compojure feels like Rack+Sinatra to me. In fact, the choice between Ruby
and Clojure to me is as trivial as using Python or PHP. Here's ClojureDoc's
tutorial on making a DB-backed webapp in Clojure: [http://clojure-
doc.org/articles/tutorials/basic_web_developm...](http://clojure-
doc.org/articles/tutorials/basic_web_development.html). It's just
Sinatra/Flask in another flavor all over again.

The only downside of Clojure so far for me is that I might already be drunk
with that special Lisp koolaid already.

~~~
adambard
It's hard to argue that the Clojure code is as easy to read as the Ruby or
Python, and if you're working with a team that matters. Not to mention the
relative dearth of Clojure-fluent developers. Neither of those hurdles is
impossible to overcome, but if you're choosing between Ruby and Clojure, you
have to justify that decision somehow.

Of course, if I'm the only developer I choose Clojure every time. That's
probably the Lisp koolaid doing its thing.

~~~
danneu
Here's what I meant:

    
    
        (def nouns #{"apple" "book" "house"})
        (def text "My apple is red.")
    
        (defn noun?
          "intersect word with nouns and see if we've got anything."
          [word]
          (not-empty (intersection (set [word]) nouns)))
    
        (defn nurblize
          [text]
          (let [words (split text #" ")]
            (map #(if (noun? %) "nurble" %) words)))
    
        (nurblize text) ;=> ("My" "nurble" "is" "red")

~~~
adambard
Yeah, that's much better than mine.

------
northisup
Weird that performance was the first reason mentioned when saying that is why
he picked clojure. Ease of development is usually the top of my list. Then
ease of hiring developers to maintain the system. Clojure is hard to dive into
and hard to hire for right now. Two big minuses in my book.

------
rboyd
I sent you a pull request. I think the Clojure version becomes more performant
still if you slurp the nouns into a set so you don't have to do a linear
traversal.

Also, I think the regex usage makes the code a little harder to digest and
comes at the cost of further unnecessary cycles.

I think this code gets even easier if you don't have to preserve the
whitespace and filter non-alpha characters. (see danneu's suggestions)

Finally, what are your thoughts on passing along the wordlist (nouns) as an
arg to nurble? This would offer referential transparency and also make the
test easier.

Btw, I think your update broke your gist (where you changed (some (partial =
w) nouns) to (not (some (partial = w) nouns)). Check that out again.

Cool post, thanks for sharing.

------
nevster
And for anyone who's interested - here it is in old-skool Java JSP style

    
    
      <%@ page import="java.nio.charset.Charset" %>
      <%@ page import="java.nio.file.*" %>
      <%@ page import="java.util.List" %>
      <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      <%!
        public String nurble(String text) throws Exception {
          text = text.toUpperCase();
          String[] words = text.toLowerCase().replaceAll("/[^a-z ]/", "").split(" ");
          List<String> nouns = Files.readAllLines(Paths.get("nouns.txt"), Charset.defaultCharset());
    
          for (String word : words) {
            if (!nouns.contains(word)) {
              String pattern = "(?i)(\\b)" + word + "(\\b)";
              String replacement = "$1<span class='nurble'>nurble</span>$2";
              text = text.replaceAll(pattern, replacement);
            }
          }
    
          return text.replace("\n", "<br>");
        }
      %>
    
      <html>
      <body>
      <h1>Your Nurbled Text</h1>
      <div><%=nurble(request.getParameter("text"))%></div>
      <p>
        <a href="/">&lt;&lt; Back</a>
      </p>
      </body>
      </html>

~~~
nevster
To my mind, besides all the import guff, the core of that code is more
readable than many of the other language implementations and just as concise.

~~~
irahul
What are you considering the core? The nurble method?

Ruby:

    
    
      configure do
        @@nouns = Set.new File.open('nouns.txt').map {|noun| noun.strip.downcase }
      end
    
      def nurble(text)
        words = Set.new text.downcase.split
        # Replace words which are not nouns with nurble.
        (words - @@nouns).each do |word|
          text.gsub! /(\b)#{word}(\b)/i, '\1<span class="nurble">nurble</span>\2' 
        end
        text.gsub(/\n/, '<br />')
      end
    

Python is similar.

Personally, I find it far more readable that the JSP sample.

~~~
nevster
Yeah - the nurble method. It's really the Clojure that looks the worst.

Taking just the boolean query to figure out whether the word is in the noun
list, I'd place them in this order from simplest to understand through to most
obfuscated

    
    
      Python: if word not in NOUNS
      Java: if (!nouns.contains(word))
      PHP: if(!in_array($word, $nouns))
      Ruby: if not @@nouns.include? word
      Clojure: if (not (some (partial = word) nouns))
    

The Python is pretty much just english

The Java is almost english except for the !

PHP is starting to get more obtuse - ! and $ and you have to know the order of
the parameters

Ruby (from the article) has @@ and ? but isn't too bad - might go above the
php

Clojure is just weird - looking at the others, I reckon a person could easily
modify and write a similar program without knowing much of the language. With
Clojure I think it requires deeper understanding.

I haven't included your Ruby in that list because it's doing a different (much
neater :) ) thing.

------
francispelland
Perhaps I am missing something, but at least the PHP example is not the most
efficient. Seems like you could easily build a nice regex with all your nouns
and replacing them in the string, rather than splitting up the text and
looping through each word. The code would be considerably shorter and likely
quite a bit more efficient.

As for your comment on you have to load up the nouns file each file, assuming
you have PHP 5.2 or later, the file will stay in memory when using APC and is
reused. You can opt to extract the content and store that in memory also.

Lastly, for performance, the code could be improved as I mentioned above, but
Apache is definitely not the way to go for performance. NGINX is the way to
go, both are very similar, but when it comes to performance NGINX is super
lightweight, fast and handles a heck of a lot more connections.

------
bandushrew
weird. he uses a mocro framework for the others but doesn't do so for php.

overall I think I prefer the python example.

~~~
adambard
PHP is the only one of them that doesn't need it.

~~~
steveklabnik
Will Passenger just serve up ERB files just like PHP? Now I'm curious.

~~~
andyl
You don't need Passenger. Just write the sinatra app, then 'ruby
<yourapp>.rb'.

~~~
irahul
He isn't talking about the sinatra example. He is talking about
apache/passenger serving .erb files directly as apache/mod_php does for .php
files.

------
minikomi
If anyone's interested here's my go approximation
<https://github.com/minikomi/go-nurble> running at <http://poyo.co:9000> for
now :)

~~~
nickpresta
Heh. Our solutions came out very similar. I clearly did something wrong, as I
thought backreferences didn't work in Go's Regexp package. Oh well. I'll
remember for next time.

<https://github.com/nickpresta/nurblizer>

------
michaldudek
Just like others say, PHP is probably the winner here:

\- You're comparing raw PHP with frameworks from other languages. Try not
using frameworks in other languages.

\- You're saying that PHP has no dependencies manager -
<http://www.getcomposer.org>

\- Using anything more than PHP for such task is an overkill.

To be clear, I'm not saying that PHP is the best language of them all. Like
all languages it has its ups and downs and there's a lot of subjectivity on
what you like and dislike. I'm just saying that such comparison isn't really
fair.

~~~
Terretta
> _\- You're comparing raw PHP with frameworks from other languages. Try not
> using frameworks in other languages._

Then try using Perl instead of PHP, maybe, so you can have fun importing CGI
libs? Because PHP _is_ a web dev framework. Don't argue with me, argue with
PHP's own history page:

"... the very first incarnation of PHP was a simple set of Common Gateway
Interface (CGI) binaries written in the C programming language... Rasmus
rewrote PHP Tools [so the] new model was capable of database interaction and
more, _providing a framework_ upon which users could develop simple dynamic
_web applications_..." -- <http://php.net/manual/en/history.php.php>

> _I'm just saying that such comparison isn't really fair._

As presented, it's absolutely fair, otherwise you're comparing a web app
framework (PHP) to languages that don't have, for example, HTTP POST handling
unless you import those libs.

------
nickpresta
Here is my submission (written in standard Go): <http://nurble.nickpresta.ca/>

Source is available here: <https://github.com/NickPresta/nurblizer>

There are some minor differences (due to Go lacking backreferences with it's
Regexp package, as far as I can tell) that I don't care to fix. Still, in ~74
lines of code, you get something pretty decent (and no external web server
required, either).

------
TamDenholm
Just a quick note that since php didnt use a microframework (because it didnt
need to) you could've used many of the ones that already exist, however,
someone wrote a clone of Sinatra for PHP called Frank[1], although the code is
old now and there are many newer, maintained microframeworks to chose from.

[1] <https://github.com/brucespang/Frank.php>

------
zdgman
What would be interesting is if the project would have randomly picked one of
the codebases to load so you could end up taking a spin on each.

------
olivier1664
Nice article. I discovered a lot thanks to you.

Note you are very nice with the PHP implementation by using a function instead
doing the foreach inside the HTML ;)

May I suggest improve the article by including: 1- an HTML template of each
framework (since each seems to have a their own syntax) 2- the JS and GO
example (since it's the trend on YC) 3- Use the Framework name instead of the
language name

------
lmcnearney
"The bit about dependencies is most interesting to me. In PHP, the problem of
dependencies is offloaded to Apache, and by extension the server host. PEAR
helps a lot, but even that’s not guaranteed to be available everywhere."

Most modern PHP projects (PHP 5.3+) are using namespaces, autoloaders, and
Composer to handle dependencies.

<http://getcomposer.org/>

------
babawere
Am not sure if you tested your `PHP` code but your regex is wrong (It returns
empty values)

<http://codepad.viper-7.com/1MFTAW>

You can also improve the php function by using nl2br and isset instead of
in_array

Here is a revised code : <http://codepad.viper-7.com/4OtoTD>

------
waxjar
Tiny error: in Ruby global variables start with $, class variables start with
@@.

------
RogerDodger_n
Here's a Mojolicious (Perl) one I did up since I was bored:
<https://gist.github.com/RogerDodger/5242390>

------
EugeneOZ
It's only about work with strings. Not very interesting - far from real web-
applications.

------
keammo1
i'm just running the php nurble() function from command line, but it seems you
should reference global $nouns within the function and use either "/\W/"
(upper case "W") or "/\s/" in the preg_split. am i missing something?

------
meric
It surprised me to see Clojure had the longest example and PHP had the
shortest.

~~~
adambard
I was working forwards from the Ruby example for all of these, so I may have
implemented the Clojure one in a weird way.

PHP cheats a bit to be the shortest, since it just looks up files to execute
and renders stdout as the result, removing the need for routing.

~~~
antihero
Well, that's one of the few "advantages" of PHP.

Though PHP allows this to happen easily, it's a fairly terrible idea for
production code - the shortest isn't always the best.

In reality you'd use a macroframework like Symfony or a micro like Ham
<https://github.com/radiosilence/Ham>

~~~
cpress
There is a microwork made from Symfony components called Silex:
<http://silex.sensiolabs.org>.

Edit: I mis-read; reworded for clarity.

------
broken_symlink
i'd be interested in seeing a go version as well just for comparison.

------
stevewilhelm
Any "language/framework comparison app" that does not include user
authentication and permissions, sessions, user specific persistent data, https
communication, queueing of long lived operations, unit tests, and scaling
across multiple instances is worse than useless.

~~~
adambard
I'd write it if you would buy the book.

~~~
stevewilhelm
Build three versions of an app with these characteristics using Ruby on Rails,
Python, and Node and I pledge I would buy the resulting book for thirty five
dollars.

Try KickStarter. I funded this book <http://s831.us/105pQB8> and I am glad I
did. I would pay at least five dollars up front to fund the writing of such a
book.

