Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Am I abusing let*?

61 views
Skip to first unread message

Danny Gratzer

unread,
Oct 21, 2012, 11:25:30 PM10/21/12
to
I am new to Scheme and had a best practices question. Is using let* in this manner:

(define (get-squares candidate)
(let* ((groups (split board 3))
(chunks (lump-n 3 groups))
(square-groups (map (lambda (l)
(split l 3))
chunks))
(squares (map flatten (flatten x))))
squares))

Is this best practice or am I abusing let* and writing this too procedurally?

Is this more Lisp-y?

(define (get-squares candidate)
(map (lambda (l)
(flatten l))
(flatten (map (lambda (l)
(split l 3))
(lump-n 3 (split board 3))))))

I feel like the first one is more readable, but feels more procedural which worries me. I am seeking to write clean functional Scheme.

Danny Gratzer

unread,
Oct 21, 2012, 11:28:21 PM10/21/12
to
Sorry that should have been square-groups, not x

Aaron W. Hsu

unread,
Oct 22, 2012, 12:40:12 AM10/22/12
to
I probably would have done it more like this:

(define (get-squares candidate)
(map flatten
(flatten
(map (lambda (l) (split l 3))
(lump-n 3 (split board 3))))))

However, I think there is an easier or nicer way to write this, but I
don't know what lump-n or split do, or even flatten in this case. If you
could define those, then it would be easier to see if there is a better
way to do this.


--
Aaron W. Hsu | arc...@sacrideo.us | http://www.sacrideo.us
Programming is just another word for the Lost Art of Thinking.

Danny Gratzer

unread,
Oct 22, 2012, 6:22:56 AM10/22/12
to
Sure thing, flatten should take in a list of lists and return a single list with all elements inside it appended together, here's the code for that

(define (flatten l)
(if (null? l)
'()
(append (car l) (flatten (cdr l)))))

lump-n Is rather poorly named, it lumps together every nth element in a list, ie (lump-n 3 '(1 2 3 4 5 6 7 8 9)) => '((1 4 7) (2 5 8) (3 6 9))

(define (lump-n n l)
(define (lump n remaining l)
(if ( = remaining 0)
'()
(cons (every-n n l) (lump n (- remaining 1) (cdr l)))))
(lump n n l))

(Perhaps this should have been done using a loop? I have only been using Scheme for 4 days so I'm still learning a lot of the available functions)

Finally split splits a list into chunks of size n

(define (split candidate size)
(if (null? candidate)
'()
(cons (first-n size candidate) (split (drop-n size candidate) size))))

(define (first-n n l)
(if (= n 0)
'()
(cons (car l) (first-n (- n 1) (cdr l)))))

(define (drop-n n l)
(if (= n 0)
l
(drop-n (- n 1) (cdr l))))

arc

unread,
Oct 22, 2012, 6:31:10 AM10/22/12
to
Why does it feel more procedural or un-functional?

A program is functional so long as procedures have no side-effects and
objects are not mutated. Your first example does neither, so it is
functional.

Remember, let* is equivalent to a sequence of lambda expressions:

(define (get-squares candidate)
((lambda (groups)
((lambda (chunks)
((lambda (square-groups)
((lambda (squares)
squares)
(map flatten (flatten x))))
(map (lambda (l) (split l 3)) chunks)))
(lump-n 3 groups)))
(split board 3)))

(I've just typed this in without checking, so chances are i've made a
mistake somewhere)

So really all you're doing is defining and executing a sequence of
lambda expressions. nothing's more functional than that! :-]

Perhaps you're worried because it looks too much like a sequence? In all
of these examples, (split board 3) has to be evaluated first, because
otherwise there's nothing to map on or flatten or whatever. (OK, so
technically the (lambda (l) (split l 3)) could be evaluated first...),
so there's going to be a sequence of events no matter how you write it.

Also, remember that map and higher-order functions aren't complusory:
they're there to make your life easier.

While Aaron's version is reasonably clear, in larger examples I think
often it's better to do things your way, using let* to name bits as you
go.

(define (otherwise things)
(lambda (can)
(end up
(being
(horribly embedded)
(lambda () (and (map rather (list difficult to follow) ;
(or (just plain))))
(confusing) ))))

Also, let* allows you to name each part of the computation, so hopefully
it will be clearer what every bit does. For example,

(map (lambda (l) (split l 3)) chunks)

Seeing that on its own for the first time gives me no inkling of what is
being done here. I mean, sure, something that splits stuff is being
mapped over some 'chunks' (which could be anything), but i don't know
why or what all this means.

Your first formulation tells me that this produces 'square-groups',
which starts to be a bit more helpful.

--
-arc.




Danny Gratzer

unread,
Oct 22, 2012, 6:53:12 AM10/22/12
to
Alright thank you! (And don't worry I'm not using map just for the sake of it it's just more convenient for these operations)

Just out of curiosity in larger pieces of Scheme code, is this pattern of let* with small incremental changes more common? I don't see it in SICP but there aren't really large chunks of code, well at least that I've seen.

Pascal J. Bourguignon

unread,
Oct 22, 2012, 7:17:24 AM10/22/12
to
What matters in larger pieces of code, is how you structure the modules
and the functions. Not how the functions are implemented.

As long as the implementation of a function does the job correctly
(proof and tests) and with controlled time and space complexity, it's
good. If you can't read it, you can always rewrite it later.


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.

Danny Gratzer

unread,
Oct 22, 2012, 7:38:51 AM10/22/12
to

Do you have any examples of well organized code like this in open source projects? I'd like to read some.

Nils M Holm

unread,
Oct 22, 2012, 7:43:43 AM10/22/12
to
Danny Gratzer <danny....@gmail.com> wrote:
> Just out of curiosity in larger pieces of Scheme code, is this
> pattern of let* with small incremental changes more common? I don't
> see it in SICP but there aren't really large chunks of code, well
> at least that I've seen.

First, I have to second what Pascal said. In large programs, the
interaction between the individual parts is more important than
local details.

Second: your usage of LET* is not unusual at all. When it seems
suitable, do it this way! There are often multiple approaches to
the same problem, and it is often a matter of style which one to
use.

What is most important is that you can still understand your own
code tomorrow, or in a month, or in a few years.

--
Nils M Holm < n m h @ t 3 x . o r g > www.t3x.org
0 new messages