[racket] Rosetta Sokoban Solution
Firstly, do you suggest I remove the comments, in which case use more
blank lines or not? I don't mind, and only added them mainly in the
hopes it would be helpful to readers as the purpose of Rosetta is to
educate. But I agree that it also kind of thickens the code.
As for Pico being less thick, this is entirely true. Off the top of my
head, I suspect it has largely to do with PicoLisp's single data
structure and use of properly lists. I had originally directly
translated the PicoLisp code and came up with a comparable solution.
Then I replaced (cdars) and such with proper struct references,
requiring the corresponding lines of define-match or match-let. Because
these are found in lists, it requires additional cons of the car of the
cdr of the struct-x of the car type statements where Pico just uses
cdadadr. Which one is better is an open question to me. What you lose
in abstraction you gain in concision. After all, we use car instead of
first, and why? car is shorter. It may happen to mean the first
element, but it actually means the car of a pair, and it fortunately
happens that lists are made of these pairs, so luckily the car for lists
/happens/ to be the same as the car for pairs, which is an
implementation detail that every Scheme luckily shares (maybe it's
explicitly in the standard, but it's still in some sense a mistake of
relying on an implementation in the purest sense, at least within the
scheme itself).
Also, for some reason Racket doesn't return values from mutations. Why
on earth (set! x 3) is void instead of 3 is a mystery, but it leads to
things like:
(define-match (x ...) (heap-min heap))
(now-remove-it!)
and, in addition to verbose naming:
(vector-set! v i 4)
v
which should ideally be:
(vec! v i 3)
Things that take 1 line tend to take 2-3 lines. The best example is:
(unless (idx '*Hist (sort (copy *Boxes)) T)
where idx goes through *Hist (a list) and, due to the #t flag at the
end, adds the sorted list of boxes if it's not found. The overall
purpose is it saves the current state to avoid duplicate searches. The
key is that, in addition to mutating history, it also /returns/
something. If the element was added, it returns either the element or
the list (I forget, but it's non-#f is the point). So this one line
continues the search by determining if the new state was added to the
history or not, based on whether it was already in the history. Since I
have to extract the minimum of a heap and then mutate the heap, not only
do I need at least 2 expressions to accomplish that, but in general,
this kind of pattern can take /three/ expressions. One to bind the
result before it's deleted forever, one to delete it, then the
expression that uses it! It might not be usable directly in the first
line since it might recurse with what should have been the deleted
version of the structure. So one might have to save the result, mutate,
and /then/ recurse!
Pico also has property lists, so instead of vector-ref'ing everything,
the 'val of a symbol is obtained (e.g. a box on a goal is the box with a
goalified 'val property). Property access function is a single
character ":" when referencing the anaphoric This. Pico has constructs
like "make" which sets up a list building environment where things can
be added on the left and right sides of a list, and then automatically
returns the list. And since all list functions can be run within make,
it is immensely powerful, essentially allowing arbitrary construction of
the primary data structure of the language, which, unlike most "real"
schemes, actually uses them directly for interpretation. One can thus
build "macros" out of expressions using the normal library functions,
instead of require-for-syntax functions that require fancy 300
character-length macro machinery like
define-syntax-transformer-for-syntax-parameterization to produce (I do
apologize for being harsh, but I like Racket and these things sadden me).
Finally, and this is more of a useless criticism since the language is
so far developed in this direction, so I apologize, but while it's nice
to have for, for/list, for*/list, for*/fold, match, define-match,
match-let, let-values-define-match*, match-let-define-values*/fold/for
(exaggerating), what tends to happen is there's a huge mess of
overlapping constructs, and a large amount of (at least for me)
programmer time wasted in deciding and refactoring between nestings of
let -> define -> cond -> or -> if vs. match-let -> if -> match -> cond
-> for/fold -> not -> and -> named let (or whatever). In PicoLisp, one
simply calls a function on what must be a list, the function names are
short, and there are a huge number of library functions. There is
almost no decision to be made! You are processing a list, and you need
to do something, so you use the corresponding library function.
PicoLisp comes built in with anaphoric stuff as well. I had to use my
custom awhen to bind @ where that is often bound for free in Pico, not
requiring a special conditional with binding (and not requiring
define-syntax-parameter and syntax-parameterize and
make-transformer-syntax-binding-case-rules to implement it...). Pico
consistently has the shorter/shortest solutions and in all my tests, has
excellent run time performance. I dare say it's a "better" language and
it would be, in my opinion, for the reasons I just gave.
My intent wasn't to start some kind of flame war here. I do like
Racket, it's teaching languages, the assocated HTDP1&2, the associated
universe/image library (I used that to do Galton Box on Rosetta), and
the fact that it's very standardized and comes with decent OpenGL
bindings that I'm using to make a 3d game that I shall one day post.
But if I had to choose which language seems to actually produce better
code, I would have to side with PicoLisp. Just beware dynamic scope.
On 06/10/2013 12:42 PM, Matthias Felleisen wrote:
> Thank you for this effort.
>
> This is not a criticism of your code but I will say that Racket code looks dense and overbearing compared to PicoLisp, Python, and Tcl. Hmph.
>
>
>
>
> On Jun 10, 2013, at 12:34 PM, Sean Kanaley wrote:
>
>> The rather empty page on Sokoban now has a Racket solution. The creator is proud to announce it's the fastest solution (though a couple didn't give run times) at ~0.1 seconds. My thanks to Ryan Culpepper for making the binary heap that made this possible.
>>
>> http://rosettacode.org/wiki/Sokoban#Racket
>>
>> Also, Racket is one short of Python in popularity. If someone could do two more we'll defeat those lazy Python programmers, probably with less work!
>> ____________________
>> Racket Users list:
>> http://lists.racket-lang.org/users
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20130610/9b5df27d/attachment-0001.html>