The same app 4 times: PHP vs Python vs Ruby vs Clojure
adambard.com
adambard.com
Problem with PHP (for me) is that you gotta install Apache to make it work.
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.)
is apache not cool anymore? i've done some pretty beast installations and apache handled them just fine.
This model is memory intensive and does not scale well.
A more accurate comparison would be something like Nginx + PHP+FPM or even HipHop VM for PHP.
If you're only measuring stick is "Upload your scripts and run, BAM!" then it's no real argument. But if you're at the scale of having professional systems engineers and scaling to millions of users then deployment isn't an issue as you have your processes running smoothly.
If you can deploy the same set up 10,000 times easily, it doesn't matter that the initial set up took you 2 hours vs. 1 hour.
Software is infinitely reproducible. The effective deployment cost then becomes 0.
Essentially, at scale, you are no longer running Apache + mod_php.
If you had 10,000 web servers, are you telling me its difficult to create a script that will automatically pull files from a centralised network store?
In the above case, that's how I would set up and its trivial.
What do you mean by standardizedPHP was quite popular in the past, and serves about 35% of the web traffic(wikipedia and facebook alone would constitute a significant amount).
I don't know about you, but my requirements are to get the job done reasonably, not "get the job done by copying a script on a cheap-ass shared hosting and you can't install any extra library. lol".
(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}))
The result is the same but the approach is different: build a set of uppercase nouns, define how each word in the text is handled, and do a single string replacement pass over the text to produce the output. Doing things like tokenizing the text and processing it as a seq or keeping the nouns lower case and producing upper case output are easy but seem like unnecessary complications so I left them out.Note: In the grand tradition of the exercise, the webserver stuff is untested. I did repl-check the nurble part but was too lazy to set up a project.clj and have leiningen pull in the webserver deps.
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.
https://github.com/adambard/nurblizer-php
https://github.com/adambard/Nurblizer
Premature optimisation is the root of all evil, right?
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.
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?
But if there would be a tutorial, perhaps this? http://www.howtoforge.com/installing-php-5.3-nginx-and-php-f...
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.
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?
I'd like to refactor the Ruby example, but I'd like to know what the proper implementation should be!
The Ruby example was already correct.
(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'?And thanks for the typo catch, too.
And, just FYI you do need to escape the backrefs or use single quotes, like:
"\\1<span class=\"nurble\">nurble</span>\\2" NOUNS = {l.strip().lower() for l in f} 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-coffeescriptI 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.... 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.
Of course, if I'm the only developer I choose Clojure every time. That's probably the Lisp koolaid doing its thing.
(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")The place I work at used to be exclusively a java shop, we introduced Clojure for a project a year ago and everybody loved it. So, far we had co-ops learn it, contractors, and the whole team. I'm just not sure who these people who can't learn Clojure are exactly.
It might be more difficult to read initially if you've only worked with one family of languages, but that doesn't make it more difficult to read inherently. In fact, there are many benefits to readability once you're familiar with the syntax. It's a lot more regular, there's less special cases than in most languages, and you can see the relations in your code visually, since you're seeing the same AST that the compiler sees.
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.
<%@ 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="/"><< Back</a>
</p>
</body>
</html>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.
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 englishThe 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.
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.
overall I think I prefer the python example.
The only reason that is true is that he chose not to use a micro framework for php.
- 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.
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.
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).
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
Most modern PHP projects (PHP 5.3+) are using namespaces, autoloaders, and Composer to handle dependencies.
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
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.
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
Edit: I mis-read; reworded for clarity.
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.