Quantcast
Channel: dave yarwood
Viewing all articles
Browse latest Browse all 64

_why's (Poignant) Guide to Ruby in Clojure: Part 3

$
0
0

Parts 1 and 2 of this series can be found here and here.

Okay, is it me, or is Chapter 5 absurdly long compared to the other chapters in this book!? Holy moley. I’m going to have to split this one up into a couple of blog entries. This entry will cover roughly half of Chapter 5.

Things start getting super fun in this chapter. I think I may have had even more fun translating these examples than I did reading/exploring them back when I was learning Ruby for the first time. These examples continue to highlight some interesting differences between Ruby and Clojure. For one thing, Ruby has the advantage when it comes to convenient “case statement” syntax. Clojure does have a case function that matches based on the identity of a given object, but Ruby’s case statements are more flexible in that they allow matching based on whether a number falls within a given range. We could mirror that syntax in Clojure fairly easily by using a macro, but it’s easy enough to just use a cond statement instead, like I did below.

I chose to use Clojure records and protocols as I translated the WishMaker and MindReader examples, however a true Clojurian might prefer to use ordinary functions rather than complicate things with records and protocols. For the purposes of this translation exercise, though, I thought it would be interesting to use records and protocols in order to show how they can duplicate some of the functionality of Ruby’s classes. By the way, “endertromb.core” is an imaginary library analogous to _why’s imaginary Endertromb module. As with some of the examples in the last chapter, the “Endertromb” examples here are more an illustration of syntax than anything.

Chapter 5 (Sections 1-3)

; ex. 1:(defn dr-chams-timeline[year](cond(= year1894)"Born."(<= 1895year1913)"Childhood in Louisville, Winston Co., Mississippi."(<= 1914year1919)"Worked at a pecan nursery; punched a Quaker."(<= 1920year1928)(str "Sailed in the Brotherhood of River Wisdomming, which journeyed the Mississippi ""River and engaged in thoughtful self-improvement, where he finished 140 credit ""hours from their Oarniversity.")(= year1929)"Returned to Louisville to pen a novel about time-travelling pheasant hunters."(<= 1930year1933)(str "Took up a respectable career insuring pecan nurseries. Financially stable, he ""spent time in Brazil and New Mexico, buying up rare paper-shell pecan trees. ""Just as his notoriety came to a crescendo: gosh, he tried to bury himself ""alive.")(= year1934)(str "Went back to writing his novel. Changed the hunters to insurance tycoons and ""the pheasants to Quakers.")(<= 1935year1940)(str "Took Arthur Cone, the Headmaster of the Brotherhood of River Wisdomming, as a ""houseguest. Together for five years, engineering and inventing.")(= year1941)"And this is where things got interesting.")); ex. 2: (dr-chams-timeline1941); ex. 3:(cond(= year1894)"Born."(<= 1895year1913)"Childhood in Louisville, Winston-Co., Mississippi.":else"No information about this year."); ex. 4:(if (= 1894year)"Born."(if (<= 1895year1913)"Childhood in Louisville, Winston Co., Mississippi.""No information about this year.")); ex. 5:(def opus-magnumtrue)(defn save-hannah[](let [successopus-magnum]nil)); this doesn't really make sense, but then, the original Ruby example was just to ; demonstrate the lack of closures in Ruby; ex. 6:(def verb"rescued")(doseq [verb["sedated""sprinkled""electrocuted"]](println "Dr. Cham"verb"his niece Hannah."))(println "Finally, Dr. Cham"verb"his niece Hannah."); ex. 7:(doseq [verb["sedated""sprinkled""electrocuted"]](println "Dr. Cham"verb"his niece Hannah."))(println "Finally, Dr. Cham"verb"his niece Hannah."); results in an error, just like the Ruby example; ex. 8:(require'[endertromb.core:asendertromb])(defprotocol WishMaking(grant[wmwish]"Grants a wish."))(defrecord WishMaker[energy]WishMaking(grant[_wish](cond (or (> (count wish)10)(re-find #" "wish))(throw(IllegalArgumentException."Bad wish."))(zero? energy)(throw(Exception."No energy left.")):else(do (endertromb/makewish)(WishMaker.(dec energy)))))); ex. 9:(def todays-wishes(WishMaker.(rand-int 6))); ex. 10:(def todays-wishes(WishMaker.(rand-int 6)))(granttodays-wishes"antlers"); ex. 11:(def number5)(print (inc number))(def phrase"wishing for antlers")(print (count phrase))(def today-wishes(WishMaker.(rand-int 6)))(granttodays-wishes"antlers"); ex. 12:(print (type5))(print (type"wishing for antlers"))(print (type(WishMaker.(rand-int 6)))); ex. 13:(require'[endertromb.core:asendertromb])(defprotocol MindReading(read [mr]"Reads minds."))(defrecord MindReader[minds]MindReading(read [_](map endertromb/readminds))); Ruby initialize method pulled out into the object instantiation:(MindReader.(endertromb/scan-for-sentience)); skipping all the irb stuff... (it's not relevant to Clojure); ex. 14:(defprotocalElevation(maintenance-password[this]"Maintenance password.")(authenticate[thispassword]"Checks password before operating."))(defrecord Elevator[password]Elevation(maintenance-password[_]"stairs_are_history!")(authenticate[_password](when-not (= passwordmaintenance-password)(throw(Exception."bad password"))))); Other Elevator methods would call (authenticate [_ password]) before executing; their functions. If the password checks out, it returns nil and the rest of the; method will happily execute. Otherwise, an exception is thrown.; ex. 15:(defn wipe-mutterings-from[sentence](clojure.string/replacesentence#"\([^)]*\)""")); ex. 16:(def what-he-said(str "But, strangely (em-pithy-dah), I learned upon, played upon (pon-shoo) ""the organs on my home (oth-rea) planet."))(wipe-mutterings-fromwhat-he-said); ex. 17:(defn wipe-mutterings-from[sentence](when-not (string? sentence)(throw(IllegalArgumentException.(str "cannot wipe mutterings from a "(typesentence)))))(clojure.string/replacesentence#"\([^)]*\)""")); ex. 18:(def something-said"A (gith) spaceship.")(wipe-mutterings-fromsomething-said); ex. 19:; (the "sentence = sentence.dup" example); there is no need to do this in Clojure, thanks to functional programming!; ex. 20:; (ditto); ex. 21:(def s"A string is a long shelf of letters and spaces.")(subs s01); A(subs s0); A string is a long shelf of letters and spaces.(apply str (drop 1(drop-last1s))); (drops first and last character)(subs s03); A s(re-find #"shelf"s); shelf; ex. 22:; (same as ex. 17); ex. 23:(def muddy-stick"Here's a ( curve.")(wipe-mutterings-frommuddy-stick); not a problem for our functional implementation using clojure.string/replace!; ex. 24:; Bingo! Here, _why uses gsub/regexp to improve this method. This is practically the; same thing as our clojure.string/replace solution; _why just used a slightly ; different regexp, \([-\w]+\) than I did. Mine's better, though ;); ex. 25:; Clojure doesn't encourage monkey-patching, but implementing this is still trivial; and quite readable using ordinary functions:(defn name-significance[name](let [legend{"Paij""Personal", "Gonk""Business", 
          "Blon""Slave", "Stro""Master",
          "Wert""Father", "Onnn""Mother",
          "ree""AM", "plo""PM"}syllables(clojure.string/splitname #"-")](clojure.string/join" "(map legendsyllables)))); BTW, you could technically "monkey-patch" the String type to add a ; "name-significance" method, extended from a protocol you create via Clojure's ; "extend-type" function, but the usage would be exactly the same: ; (name-significance "name"). So in this case, there isn't much point to using ; anything more complicated than an ordinary function.; ex. 26:(name-significance"Paij-ree"); ex. 27:; rather than monkey-patching, you could just do this:(defn dash-split[s](clojure.string/splits#"-")); if polymorphism is needed, you can use multimethods; ex. 28:(dash-split"Gonk-plo"); ex. 29:; (class String; def dash_split; split('-'); end; end); The equivalent to this in Clojure would involve using extend-type on Java's String; class. It's not really necessary, though :); ex. 30:; the equivalent part of our name-significance method is:(map legendsyllables); ex. 31:(def cats-and-tips(map #(+ %(* %0.20))[0.120.630.09]))

Hope you enjoyed this as much as I enjoyed translating it! Comment below if I missed anything or if you think you might have a better translation for any of these examples. The rest of Chapter 5 is coming soon – get ready for the Animal Lottery!


Viewing all articles
Browse latest Browse all 64

Trending Articles