[racket] "lab notebook on learning process" (was: Re: Macros baffle me)

From: Sean Kanaley (skanaley at gmail.com)
Date: Mon May 5 11:58:17 EDT 2014

“don't forget:
call-by-value! call-by-value!” somewhere early on.

Don't feel bad, fellow i-wish-racket-was-as-awesome-as-haskell-ian, I
submitted an incorrect bug report claiming something like
(function-call (const (random))) wasn't passing different random values,
where it was kindly pointed out that call-by-value insures it wouldn't.
 There *is* lazy racket.  The issues you have with for/and, etc. is a
fundamental issue with strict evaluation vs. either lazy evaluation or no
evaluation (macros).  One cannot correctly short circuit out of a loop
that's already preprocessed every possible result!

I had tried to implement Haskell's monoid type class (and type classes in
general), but mappend doesn't work in a strict language. There's no way to
pass the arguments to the Or monoid without evaluating them, and any
macro/thunk-based solution has this restraint propagate outwards.  Anything
calling mappend has to not evaluate its arguments either, or they will be
pre-evaluated before being Or-mappended, e.g. mconcat.  Then I tried it in
lazy racket and couldn't get the macros for the type classes to even
compile.  Something about begin != lazybegin != begin-for-syntax !=
begin-for-lazy-syntax-but-itself-is-strict or who knows...

On the other hand, a few weeks ago I did ICFP 2007 contest for fun in
Haskell.  It took me several hours to fix a "bug" (turned out to be a space
leak) caused by laziness.  It turned out that my flood fill loop was
thunk-ifying a tail recursive function that I had defined with an internal
let.  Using a couple bang patterns changed a minute long wait for "out of
memory" into an efficient loop.  In a real live contest or with real
customers waiting, I imagine using a
who-knows-what's-actually-happening-language carries considerable risk to
the developer...

So there are tradeoffs.  To me, Haskell with its type system (and the
desired overloading) and more uniform evaluation (always lazy instead of
function vs. macro for things like and/or) is the "better" language, but to
be honest I'm not sure I would want my life to depend on making software in
it.  So I find myself using Racket a lot as a usually trustworthy
alternative.  There have been a couple of literally unbelievable bugs, like
in one case where opening a tab in DrRacket changes the results running a
test module in another tab.

At present, my roommate and I are building side-by-side solutions in Visual
Basic and Racket (RESPECTIVELY, thank you).  It's a front-end for a matlab
simulation -- a simple GUI with few complications.  Since that's actually
what basic is good at, the number of lines of code are actually almost the
same, but each new feature I've added in about 1/3 the time, spending the
other 2/3 debugging his:

public sub function (byref dim integer sub1 as ref, byval integer(,) ref as
mouseeventargs of systemeventhandler) as function as refdimval
   omgtherearesomanykeywordsandstuffthatare40characterslong = "string1"
   thisismakingmyeyesbleed = "string2"
   ...
   exit function return for
end sub function ref dim functionsub

It takes an average of 15 keywords * 5 characters average = 75 characters
to setup the definition of a function (I made that statistic up, but it's
probably close).  Racket uses (define ()) = 11, Haskell uses "=" = 1.  So
the winner is Haskell, at 1/75 the verbosity of basic!  What's worse, is
because you *really* don't want to have to define a new function in basic,
you avoid programming functionally!  So he has all kinds of global
variables to save typing byref dim integer refval functionsub seriouslywtf
end exit just to add 1 to something.  But he gets to drag controls around
on the form, where there's almost no way to explicitly position things in
Racket, and it has to be done with code.

So all 3 of these languages have strikes against them.  I would still vote
in favor of Racket in very many situations.

Lastly, without meaning to be rude, as it's coming from the point of view
of someone who's had similar issues, there's a degree of RTFM present.
 I'll share one other story...in developing a game library for Racket, I
ran into a mystery bug that I eventually realized seemed to be the fault of
Racket and not me.  Despite repeatedly simplifying C code into basically
just pure debug output, it still wasn't doing what it was supposed
to...apparently the linked library wasn't even being loaded at all.  ...And
it wasn't.  But upon scrolling through the FFI documentation, there was
some little line like "btw DrRacket doesn't reload libraries when run is
clicked, you have to restart DrRacket".  So technically it was my fault,
but the documentation could benefit from some kind of "WARNING!" section on
each or at least especially dangerous pages for such pitfalls.  Imagine if
your car explodes if you have the break depressed while turning the AC
dial.  Probably don't want that info on page 87 subsection a.1.c.  But
since there's no way to anticipate all potential user bugs, it's not really
the fault of the documentation writers either.  So who/what is at fault?
 Why did it happen?  I would say the inefficiencies present in attempting
to plow through new material at breakneck speed.  I was able to compute
matrix multiplication in C wrapped with Racket in almost no development
time at all, except the bug -- ***and I had to learn how to do it as I went
along***.  With the internet and high powered computational devices, humans
can learn or at least think they've learned and do more than they ever
could in history.  I don't think we're smart enough to handle our own
output sometimes.  My point is that, to some degree, I think some of the
perceived issues you or I have with Racket is simply due to it being
different (and very (as in "too" (but not with a negative connotation))
complex).  It's just *very* powerful stuff, and human's fuzzy way of
thinking doesn't always mesh 1:1 with the 90 billion lines of precise
under-the-hood code present.  It's not Haskell, and so the reaction is it's
stupid because it's not Haskell, but it's intentionally *not* Haskell or it
would be a useless clone.  So it's up to the user to learn it as Racket and
not why-isn't-it-Haskell-Racket.  And thank god, because it would take 15
minutes to insert an impure debug statement.

By the way, speed is improved when using the "racket" command-line command
as opposed to running in DrRacket for whatever reason, especially when
using big numbers, by an astounding amount sometimes.  I myself recently
learned that...
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20140505/822e8148/attachment.html>

Posted on the users mailing list.