Logo, Ruby & JavaScript
I’ve been teaching my eldest daughter to program in Logo over the summer. Brian Harvey has posted PDF files for a set of excellent books on learning to program in Logo on his web site. The Berkeley version of Logo he’s produced is really excellent. It’s not just your typical turtle graphics language; it has arrays, macros, file processing, graphics, etc.
While perusing his site, I came across a tiny Logo program that demonstrates a little of its power. I was curious what it would look like in Ruby, so I ported it, then I had to see what it looked like in JavaScript.
The formatting is a little messed up due to Wordpress, but each of the three choices functions is 4 lines long.
If anyone wants to add other languages, that would be great!
See the sample page for example output.
Logo
to choices :menu [:sofar []]
if emptyp :menu [print :sofar stop]
foreach first :menu [(choices butfirst :menu
sentence :sofar ?)]
end
choices [[small medium large]
[vanilla [ultra chocolate] lychee [rum raisin] ginger]
[cone cup]]
UPDATE 9/2/07: Got an even more concise solution from Brian Harvey:
to choices :menu foreach crossmap "sentence :menu "print end
Ruby
def choices menu, sofar=[]
if menu.empty?: puts sofar.join(’ ‘)
else menu[0].each {|item| choices(menu[1..-1],
sofar + [item]) } end
end
choices [['small', 'medium', 'large'],
['vanilla', 'ultra chocolate', 'lychee', 'rum raisin', 'ginger'],
['cone', 'cup']]
JavaScript
function choices(menu, sofar) {
if (emptyp(menu)) print(sofar);
else foreach(menu[0],
function (x) {
choices(menu.slice(1), sofar.concat(x)); });
}
choices([['small', 'medium', 'large'],
['vanilla', 'ultra chocolate', 'lychee', 'rum raisin','ginger'],
['cone', 'cup']], []);
I had to create a few helpers for the JavaScript version:
function emptyp(a) {
return a.length === 0;
}
function print(list) {
foreach(list, function (x) { document.write(x + ' '); });
document.write('<br />');
}
function foreach(arr, f) {
for (var idx in arr) { f(arr[idx]); }
}
UPDATE: Use <pre> </pre> tags to allow easier formatting of code. Bummer, I just discovered that Wordpress strips the <pre> tags from normal users
I’ll go ahead and wrap code with it as they come in, so if your comment looks bad at first, it’ll be cleaned up shortly.

August 31st, 2007 at 9:21 pm
Jordan L. contributed this Java version (I added class wrapper and normalized choice strings).
import java.util.*; public class Choices { public static void choices(List<Object[]> choices, String sofar) { if (choices.isEmpty()) System.out.println(sofar); else for (Object choice : choices.get(0)) choices(choices.subList(1, choices.size()), sofar + choice.toString() + " "); } public static void main(String[] args) { choices(Arrays.asList(new Object[][] { { "small", "medium", "large" }, { "vanilla", "ultra chocolate", "lychee", "rum raisin", "ginger" }, { "cup", "cone" } }), ""); } }September 1st, 2007 at 1:29 pm
Here’s a Scheme version from a friend in Ohio that I tweaked just a bit.
(define (choices menu sofar) (cond [(null? menu) (for-each (lambda (word) (printf "~a " word)) sofar) (newline)] [else (for-each (lambda (item) (choices (cdr menu) (append sofar (list item)))) (car menu))])) (choices (list '(small medium large) '(vanilla "ultra chocolate" lychee "rum raisin" ginger) '(cone cup)) '())September 1st, 2007 at 3:58 pm
Here’s a Common Lisp version, but I’m hoping some kind soul on comp.lang.lisp will provide me with pointers to make it cleaner. Especially with the print-choice function.
(defun print-choice (lst) (dolist (i lst) (format t "~a " i)) (format t "~%")) (defun choices (menu sofar) (if menu (dolist (i (car menu)) (choices (cdr menu) (append sofar (list i)))) (print-choice sofar))) (choices (list (list "small" "medium" "large") (list "vanilla" "ultra chocolate" "lychee" "rum raisin" "ginger") (list "cone" "cup")) '())September 1st, 2007 at 4:33 pm
PHP:
function choices($menu, $sofar) { if (count($menu)) foreach ($menu[0] as $item) choices(array_slice($menu,1), $sofar."$item "); else echo "$sofar\\n"; } choices(array( array("small","medium","large"), array("vanilla","ultra chocolate","lychee","rum rasin","ginger"), array("cone","cup")), "");September 1st, 2007 at 8:33 pm
OK, here’s a C# version. No need for a namespace declaration if we’re OK running in the default namespace. No need for a using statement either, if you fully qualify your types.
Chip H.
class Program { static void Main(string[] args) { string[] sizes = new string[] { "small ", "medium ", "large " }; string[] flavors = new string[] { "vanilla ", "ultra chocolate ", "lychee ", "rum raisin ", "ginger " }; string[] servings = new string[] { "cone", "cup" }; foreach (string size in sizes) foreach (string flavor in flavors) foreach (string serving in servings) System.Console.WriteLine(size + flavor + serving); } }September 1st, 2007 at 9:09 pm
Since it would be trivial to translate the Ruby version directly to Python, I thought I’d approach the problem from a completely different angle. Here’s my 4-line Python version:
September 1st, 2007 at 9:27 pm
Scott, extra points for creativity
Darn you for causing me to translate that back to other languages now…
September 1st, 2007 at 11:34 pm
Here’s a direct translation of Scott’s Python solution to Ruby. I had to write the reduce function because I wasn’t aware of any built-in Ruby function that was close.
I like the Python lambda invocation better than having to say x.call() in Ruby.
def reduce fn, lst return lst if lst.length < 2 result = fn.call(lst[0],lst[1]) return lst.length == 2 ? result : reduce(fn, [fn.call(lst[0],lst[1])] + lst[2..-1]) end optstr = lambda {|x| x.instance_of?(String) ? x : x.join(' ')} prod1 = lambda {|elem, vec| vec.map {|x| optstr.call(elem)+' '+optstr.call(x)}} xprod = lambda {|vec1, vec2| reduce(lambda {|a, b| a+b}, vec1.map {|elem| prod1.call(elem, vec2)})} choices = lambda {|x| reduce(xprod, x).join("\\n")} q = [ ['small','large'], ['vanilla', ['ultra', 'chocolate'], ['rum', 'raisin']], ['cone', 'cup'], [ 'here', 'to go'] ] puts choices.call(q)September 2nd, 2007 at 12:00 am
My bad. Proc#[] is a synonym for Proc.call, so the following will work:
choices[q]
instead of:
choices.call(q)
I’m not sure it’s much of an improvement though, since it makes the function call look like an array reference! I think Scheme wins out here for consistency:
(expr arg1 arg2 … )
whatever expr evaluates to is executed as a function call:
((if (< 1 2) + -) 3 4) -> 7
September 2nd, 2007 at 1:28 am
Lisp translation of Scott’s Python translation of the Logo program
Had to write a join-string function since I couldn’t find a Lisp equivalent of Ruby’s list.join(’ ‘) or Python’s ‘ ‘.join(list)
I’m very much a Lisp newbie, but I was surprised how straightforward the port went (except for getting stuck on join-string and having to research to find #\Newline - argh). I’m curious, given a large set of algorithms, whether it would be easier to port Python/Ruby to Lisp or vice versa given equal language skills.
;; Helper function (defun join-string (sep lst) (if (cdr lst) (concatenate 'string (car lst) (string sep) (join-string sep (cdr lst))) (car lst))) (defun prod1 (elem vec) (mapcar #'(lambda (x) (join-string " " (list elem x))) vec)) (defun xprod (vec1 vec2) (reduce #'(lambda (a b) (concatenate 'list a b)) (mapcar #'(lambda (elem) (prod1 elem vec2)) vec1))) (defun choices (x) (join-string #\\Newline (reduce #'xprod x))) (princ (choices (list (list "small" "large") (list "vanilla" "ultra chocolate") (list "cone" "cup") (list "to go" "eat in"))))September 2nd, 2007 at 10:47 am
My first instinct in python turns the cross product function inside out which may not optimize as well. Shame, because it feels a lot more readable to me.
def cross(aa): if len(aa) == 0: return [()] else: return sum([[(x,) + y for x in aa[0]] for y in cross(aa[1:])], []) def choices(menu): return "\\n".join(" ".join(x) for x in cross(menu)) print choices([['small', 'medium', 'large'], ["vanilla","ultra chocolate","lychee","rum rasin","ginger"], ['cup', 'cone']])September 2nd, 2007 at 3:08 pm
Wow. I asked for a translation of the Python code back to Logo, and Brian Harvey responded with this:
Is that concise or what?! I was thinking of creating yet another web framework in Lisp, but maybe I should use Logo?
http://groups.google.com/group/comp.lang.logo/browse_frm/thread/96b5071c0e06b5b8/#
September 3rd, 2007 at 8:47 am
Please forgive my amature Haskell. I don’t know whether there is a cross product function in one of its libraries, but writing one isn’t too hard.
choices (xs:[]) = xs choices (xs:xss) = foldr ((++) . (\\ys -> map (++ " " ++ ys) xs)) [] (choices xss) main = putStr (unlines( choices [["small", "medium", "large"], ["vanilla","ultra chocolate","lychee","rum rasin","ginger"], ["cup", "cone"]] ))Note from Brian: this didn’t seem to work with hugs, so I used ghc with no problems. 1) save code to choices.hs, 2) ghc choices.hs, 3) ./a.out
September 3rd, 2007 at 11:24 am
Thanks for the haskell version Ben. Now if someone will contribute an Erlang version, we’ll have a pretty full set
September 4th, 2007 at 11:45 pm
Got a helpful response on comp.lang.lisp that replaced my print-choice function with a simple format. I’m really liking Common Lisp:
(defun choices (menu &optional (sofar '())) (if menu (dolist (i (car menu)) (choices (cdr menu) (append sofar (list i)))) (format t "~{~a~^ ~}~%" sofar))) (choices (list (list "small" "medium" "large") (list "vanilla" "ultra chocolate" "lychee" "rum raisin" "ginger") (list "cone" "cup")))September 5th, 2007 at 6:26 am
Prolog:
choices([[small, medium, large],
[vanilla, 'ultra chocolate', lychee, 'rum raisin', ginger],
[cone, cup]]).
Example query:
?- choices(Cs), maplist(member, C, Cs), maplist(format(”~w “), C), nl, fail.
small vanilla cone
small vanilla cup
small ultra chocolate cone
etc.
September 6th, 2007 at 9:07 pm
A Smalltalk version.
“First, a new subclass called Counter was constructed from the Array class. Two variables were added to the class: slave and value. The following methods were added.”
initialize slave := nil. value := 1. ^self increment value := value + 1. value > self size ifTrue: [value := 1. slave notNil ifTrue: [slave increment]] last ^value = self size and: [slave isNil ifFalse: [slave last] ifTrue: [true]] slave ^slave slave: anObject slave := anObject value Transcript show: (self at: value); show: ' '. slave notNil ifTrue: [slave value]. ^value. "The choices program." counter := Counter withAll: #('small' 'medium' 'large'). counter slave: (Counter withAll: #('vanilla' ultra 'chocolate' 'lychee' 'rum raisin' 'ginger')). counter slave slave: (last := (Counter withAll: #('cone' 'cup'))). [counter last] whileFalse: [x value. x increment. Transcript cr]. x valueSeptember 8th, 2007 at 4:22 am
Sorry - a typo in the Smalltalk program: left out a ‘ before ultra (for ‘ultra chocolate’).
Also, the “last :=” is an artifact from a previous version, and while it causes no harm whatsoever, it can be removed.
This is the cleaned up version. I ask that the moderator substitute this for the version above - THANKS!
“The choices program.”
counter := Counter withAll: #(’small’ ‘medium’ ‘large’).
counter slave: (Counter withAll: #(’vanilla’ ‘ultra chocolate’ ‘lychee’ ‘rum raisin’ ‘ginger’)).
counter slave slave: (Counter withAll: #(’cone’ ‘cup’)).
[counter last] whileFalse: [x value. x increment. Transcript cr].
x value
September 30th, 2007 at 10:18 am
Clean:
items =: [["small","medium","large"],["vanilla","chocolate"],["cup","cone"]]
menu i
| i == [] = “”
= hd i +++ “\n” +++ menu (tl i)
Start = menu [x+++” “+++y+++” “+++z \\ x
January 5th, 2008 at 4:37 pm
[...] August, I wrote a blog post comparing a short function (from Brian Harvey’s home page) in Logo and several other [...]
January 31st, 2008 at 11:28 am
I thought I’d put a first draft up of a version in Arc
(def joinstr (lst (o sep " ")) (if lst (string (car lst) (apply string (map [string sep _] (cdr lst)))) "")) (def choices (menu (o result '())) (if menu (each x (car menu) (choices (cdr menu) (cons x result))) (prn (joinstr:rev result)))) (choices (list (list "small" "medium" "large") (list "vanilla" "ultra chocolate" "lychee" "rum raisin" "ginger") (list "cone" "cup")))January 31st, 2008 at 5:41 pm
After helpful feedback on the Arc forum and discovering join:
(def choices (menu (o result '())) (if menu (each x (car menu) (choices (cdr menu) (join result (list x)))) (prall result "\n" " "))) (choices '( ("small" "medium" "large") ("vanilla" "ultra chocolate" "lychee" "rum raisin" "ginger") ("cone" "cup")))February 2nd, 2008 at 12:01 pm
Sorry - couldn’t help writing another Haskell version:
choices :: [[String]] -> IO ()
choices xss = mapM_ (putStrLn . unwords) (f xss)
where f [] = return []
f (zs:zss) = do x
February 2nd, 2008 at 12:04 pm
I hardly ever use HTML, so unsurprisingly I screwed up my first attempt to post a comment. Sorry (again)!
choices :: [[String]] -> IO () choices xss = mapM_ (putStrLn . unwords) (f xss) where f [] = return [] f (zs:zss) = do xFebruary 2nd, 2008 at 12:29 pm
Having just usefully taught myself how to use ampersands etc. in order to prevent “<-” being parsed as a comment, I can finally unveil my Haskell version.
Thanks for your patience. (And fingers crossed for third attempt…)
Ben’s Haskell version is shorter, of course, but I think mine might be more ‘idiomatic’ in that it abides by old Haskell maxim: ‘use monads whenever you see the slightest opportunity to show off the fact that you know how they work’.
choices :: [[String]] -> IO () choices xss = mapM_ (putStrLn . unwords) (f xss) where f [] = return [] f (zs:zss) = do x <- zs xs <- f zss return $ x:xs main = choices [["small", "medium", "large"], ["vanilla", "ultra chocolate", "lychee", "rum raisin", "ginger"], ["cone", "cup"]]February 2nd, 2008 at 1:22 pm
One more:
November 10th, 2008 at 8:13 pm
There are some APL, J & K versions of this program in this comp.lang.apl thread. Here is a version in J:
Options=: ;: each ('small large medium');('ginger lychee rum_raisin ultra_choc vanilla');('cone cup') choices=: monad : ';:^:_1 >,{ y' choices Options small vanilla cone small vanilla cup small ultra_choc cone ... large rum_raisin cup large ginger cone large ginger cupNovember 10th, 2008 at 9:04 pm
APL: ?,??.{?,' ',?}/('small' 'medium' 'large')('vanilla' 'ultra chocolate' 'lychee' 'rum raisin' 'ginger')('cone' 'cup') J (2 solutions): >,{ ('small';'medium';'large');('vanilla';'ultra chocolate';'lychee';'rum raisin';'ginger'); , { options