I was curious what it would look like in Ruby, and I was surprised at
the similarity, so I thought I'd share it.
Of course, given Crockford's comments about JavaScript being closer to
Scheme than Java, I had to port it to JavaScript also, but I had no
desire for a Java version!
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]]
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']], []);
// supporting functions for JavaScript
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]); }
}
Wow. I thought I'd crank out a Java version, but it was just too
painful :) I programmed in Java from '96 to '06, but after spending a
year and a half programming Ruby, and now learning Logo/Scheme/Lisp, I
hope I never have to earn a living from Java!
Wow, talk about synchronicity. Just today I got an email from a former
student who sent me a Python translation of the same example!
Well send him over to http://lojic.com/blog/2007/08/31/logo-ruby-javascript
to add it to the collection :)
Not sure what your student's Python version looks like, but a friend
of mine just submitted one that's pretty unique:
optstr = lambda x : (type(x)==str)*str(x)+(type(x)!=str)*' '.join(x)
prod1 = lambda elem, vec : map(lambda x : optstr(elem)+'
'+optstr(x), vec)
xprod = lambda vec1, vec2 : reduce(list.__add__,map(lambda elem :
prod1(elem ,vec2), vec1))
choices = lambda x : '\n'.join(reduce(xprod, x))
q = [['small','medium','large'],
['vanilla', ['ultra', 'chocolate'], 'lychee', ['rum', 'raisin'],
'ginger'],
['cone', 'cup']]
print choices(q)
Anyone care to translate this back to Logo? Here's a translation of
the above to Ruby in case anyone is more familiar with that. I had to
write reduce since Ruby doesn't have it :(
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)
Sure:
to choices :menu
foreach crossmap "sentence :menu "print
end
That's it -- FOREACH and CROSSMAP are in the UCBLogo library, and SENTENCE,
which does the joining of words, is primitive in every Logo.
To use it, you say
choices [[large medium small]
[vanilla [ultra chocolate] lychee [rum raisin]]
[cone cup]]
I didn't do it this way on my web page partly because I thought it was easier
to understand the other way (using recursion instead of CROSSMAP) and partly
because I think it's the word/sentence data types that *really* give Logo its
advantage here. In those other languages, you're constructing a long string
of characters that includes N-1 spaces, not N spaces, for N words; in Logo,
you don't think about spaces at all, because conceptually you have a sequence
of words, not a sequence of characters.
Another slight advantage we have is that it's CROSSMAP, not just CROSSPRODUCT,
in the Logo library, so I can do the combining of elements and the flattening
of the result in a single step. Otherwise I would have had to say
map [apply "se ?] crossproduct :menu
or something like that.
Amazing :) My appreciation of Logo continues to grow. I started out
using it to teach my daughter to program and had the stereotypical
impression of Logo (turtle graphics, etc.), but beginning to work
through CSLS and the manual has truly expanded my impression.
Maybe it's the fact that my comfort zone is with infix syntax
(currently Ruby), but being able to combine prefix and infix seems
quite nice. Although, it does introduce a Perl-like "there's more than
one way to do it" issue.
> To use it, you say
>
> choices [[large medium small]
> [vanilla [ultra chocolate] lychee [rum raisin]]
> [cone cup]]
>
> I didn't do it this way on my web page partly because I thought it was easier
> to understand the other way (using recursion instead of CROSSMAP) and partly
> because I think it's the word/sentence data types that *really* give Logo its
> advantage here. In those other languages, you're constructing a long string
> of characters that includes N-1 spaces, not N spaces, for N words; in Logo,
> you don't think about spaces at all, because conceptually you have a sequence
> of words, not a sequence of characters.
I agree about the convenience of sentence and word. Has anyone created
a Lisp package to allow programming with UCBLogo in Common Lisp? I'm
still very much a newbie to Logo and Lisp, but from my reading, it
seems Common Lisp's macro capabilities would handle this. If one
doesn't exist, it sounds like a good learning project for me. My
interest in this would be to gain the efficiency of a Lisp compiler.
I don't know about CL, but there's a word/sentence package for Scheme:
Brian> to choices :menu
Brian> foreach crossmap "sentence :menu "print
Brian> end
If each sublist has two items in it, and the number of sublists is n,
then there will be 2^n lines to print out. It seems to me that the
recursive version is using O(n) storage while the crossmap version is
using O(2^n) storage (in the case of sublists of length 2). I suspect
that for any given memory size, it wouldn't be difficult to find an n
such that the recursive version would work but the crossmap version
would not work.
Andru
--
Andru Luvisi
Quote Of The Moment:
If we don't succeed, we run the risk of failure.
Indeed, that's another reason to prefer the recursive version. I never
remember to do that sort of analysis; I'm secretly a hacker, not a computer
scientist, at heart. :-) But the crossmap version is the direct translation
of that other version that started this subthread.