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

Functional programming and local variables.

16 views
Skip to first unread message

Thaddeus L Olczyk

unread,
Jan 13, 2002, 4:01:34 PM1/13/02
to
I posted a question to an emacs newsgroup recently along with some
code. Someone came back saying that I should make my code more
"functional". The complaint, he fealt I used setf too much. The
example looked like

(defun foo()
(let ((x nil))
(if (some-cond))
(setf x retval1)
(setf x retval2)
)
(do-some-processing)
x
)
)
his complaint was that I used setf to assign values to x.
To me it doesn't seem to make much of a difference.
Advice?

Ray Blaak

unread,
Jan 13, 2002, 4:22:07 PM1/13/02
to
olc...@interaccess.com (Thaddeus L Olczyk) writes:
> I posted a question to an emacs newsgroup recently along with some
> code. Someone came back saying that I should make my code more
> "functional" [...]

>
> (defun foo()
> (let ((x nil))
> (if (some-cond))
> (setf x retval1)
> (setf x retval2)
> )
> (do-some-processing)
> x
> )
> )
> his complaint was that I used setf to assign values to x.
> To me it doesn't seem to make much of a difference.
> Advice?

(defun foo ()
(let ((x (if (some-cond) retval1 retval2)))
(do-some-processing)
x))

This code is cleaner, easier to understand, and more likely to avoid errors.

--
Cheers, The Rhythm is around me,
The Rhythm has control.
Ray Blaak The Rhythm is inside me,
bl...@telus.net The Rhythm has my soul.

mda...@andrew.cmu.edu

unread,
Jan 13, 2002, 4:24:20 PM1/13/02
to

It seems much more concise and clear to write:
(defun foo ()
(let ((x (if (some-cond)
retval1
retval2)))
(do-some-processing)
x))

Than what you have above. There is nothing wrong with using side-effects
if you are careful about it. But in your case it seems like unnecessary
code-obfuscation.

If I may add a small note on style, it is considered unaesthetic to place
a single parenthesis on a line by itself. Also, have you considered using
an editor that performs parenthesis-matching, automatic indentation,
and S-expression-based movement commands? It would eliminate the need
to place parenthesis on lines by themselves, for sure. I used VIM to
indent the above code but I use Emacs quite a bit. You should consider it,
it will make your life infinitely easier.

--
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Matthew Danish email: mda...@andrew.cmu.edu ;;
;; OpenPGP public key available from: 'finger m...@db.debian.org' ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Barry Margolin

unread,
Jan 13, 2002, 4:45:06 PM1/13/02
to
In article <3c48f485....@nntp.interaccess.com>,

No, it doesn't, IMHO. The alternative is:

(defun foo ()
(let ((x (if (some-cond) retval1 retval2)))
(do-some-processing)
x))

I don't like putting long initializations in LETs (it clutters up the
variable initializations section, which can make it hard to read if several
variables are being ininitialized), so if the IF expression is complex I
think your way is probably clearer.

It could actually be written without the local variable at all:

(defun foo ()
(prog1
(if (some-cond) retval1 retval2)
(do-some-processing)))

However, I'm not a big fan of using PROG1. It's mainly a historical
remnant from the days before LET, so creating local variables wasn't as
convenient.

--
Barry Margolin, bar...@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.

Kenny Tilton

unread,
Jan 13, 2002, 5:39:53 PM1/13/02
to

Yes, this stuff matters. Perhaps a very small bit of bad code does not
make that apparent. The problem is that writing ten lines this way leads
to ten thousand being written this way, because ten thousand lines get
written ten at a time and why should you take any more care with the
next ten if you are going to be so blithe about a bad job on these ten?

Genius is in the details. I have no idea what that means, but it might
apply here.

:)

kenny
clinisys

Tim Bradshaw

unread,
Jan 13, 2002, 6:00:35 PM1/13/02
to
* Thaddeus L Olczyk wrote:

> (defun foo()
> (let ((x nil))
> (if (some-cond))
> (setf x retval1)
> (setf x retval2)
> )
> (do-some-processing)
> x
> )
> )

I think that masses of SETFs are kind of a bad sign, although the kind
of extreme counter example of code like:

(let ((foo (let ((interim (compute-big-form)))
... more code ...)))
...)

Is also pretty hard to read (I tend to generate code like this latter
all the time). And of course there's the issue of

(if ...
(setf ...)
(setf ...))

vs

(setf (if ... ... ...))

I'd almost always use the latter as well but it's obviously just a
style issue.

What I think is definitely bad is an initial explicit binding followed
by an immediate assignment, which you have:

(let ((x nil))
(setf x ...)
...)

should be

(let (x)
(setf x ...)
...)

But mostly I think it's all just a matter of taste and how hard the
code is to read. Extremely functional code can be pretty hard to read
even if it's `pure'.

--tim

Hirotaka Yamamoto

unread,
Jan 13, 2002, 6:20:36 PM1/13/02
to
Shouldn't it be

(let ((x ...))
...)

?

Yamamoto

Dr. Edmund Weitz

unread,
Jan 13, 2002, 6:50:18 PM1/13/02
to
Hirotaka Yamamoto <ym...@yc5.so-net.ne.jp> writes:

> Shouldn't it be
>
> (let ((x ...))
> ...)

No, not necessarily so. LET is followed by a list whose elements are
of the form (VAR INIT-FORM) or (VAR) or just VAR. In the latter two
cases VAR is initialized to NIL. See the CLHS for details.

Edi.

Kaz Kylheku

unread,
Jan 13, 2002, 7:18:53 PM1/13/02
to
In article <3c48f485....@nntp.interaccess.com>, Thaddeus L Olczyk wrote:
>I posted a question to an emacs newsgroup recently along with some
>code. Someone came back saying that I should make my code more
>"functional".

The request to modify code must be accompanied by a rationale, which
identifies as many risks, costs and benefits as possible.

Making code ``more functional'' is not a benefit in itself, except
to those who blindly believe in a functional programming religion.

However, making a particular code make better use of the functional
programming approach, especially when it is already *close* to it,
might have the benefits of making the code clearer, easier
to understand, maintain and so on. Destructively manipulating
local variable might also have the benefit of making code clearer.
It depends on the actual case in contention.

These are only subjective benefits; nobody has an exclusive frame of
reference for judging these attributes absolutely, but concensus can be
reached within a group.

What is functional programming really about? Is it about avoiding all
explicit assignments? Or is it about designing *interfaces* which
don't have the semantics of assignment, on a coarser granularity?
For example, MAPCAR can be used as an element of functional programming.
It is undeniably functional in nature. But its *implementation* may
destructively manipulate local varibles. Who cares what it does in its
guts as long as it doesn't change the input object? How it achieves its
function is opaque to the user.

And of course, any program which does anything destructively manipulates
something, even if it's something that happens implicitly, out of the
programmer's concern.

>The complaint, he fealt I used setf too much. The
>example looked like
>
>(defun foo()
> (let ((x nil))
> (if (some-cond))
> (setf x retval1)
> (setf x retval2)
> )
> (do-some-processing)
> x
> )
>)
>his complaint was that I used setf to assign values to x.
>To me it doesn't seem to make much of a difference.

All you did here is use the variable to make the program simpler
to understand. The symbol x is simply used as a connection between
computing some value and later returning it.

To connect these things implicitly into a unified functional expression
might reduce that clarity. Sometimes it is better to just have things
laid out in pieces, and use names to make the semantic connections
between the pieces. That is why we, for instance, separate a program
into small functions, which are connected by mentioning each other's
names, rather than making one big function.

And of course the overall function is functional. It doesn't take any
parameters, and doesn't modify any global state (from what anyone
can tell---maybe do-some-processing does that). It calls some other
things and passes through the result. The caller doesn't have
to be concerned about any side effects, right?

>Advice?

Express your understanding for the benefits of a functional approach to
programming. Explain that the use of a few local variables to unravel
what would otherwise be some deeply nested expressions has the benefit
of clarifying a function, without detracting from the benefit of
the functional approach: the overall semantics of the function stil
form a functional capsule.

Then ask for a cost, risk and benefit analysis for changing
the existing code to reduce or eliminate setf, if he still believes
that there are undeniable benefits.

If you can't come to a reasonable agreement with this individual,
you may have to suspect his or her ability to reason using facts, rather
than beliefs and opinions. He or she may ultimately be toxic to any
undertaking of yours that he or she is involved in, so in any long term
plans, you will have to take that into account.

Coby Beck

unread,
Jan 13, 2002, 8:11:34 PM1/13/02
to

"Kenny Tilton" <kti...@nyc.rr.com> wrote in message
news:3C420DE4...@nyc.rr.com...

> Genius is in the details.

I thought that where the devil was... ;-)

--
Coby
(remove #\space "coby . beck @ opentechgroup . com")


Erik Naggum

unread,
Jan 13, 2002, 8:48:49 PM1/13/02
to
* Thaddeus L Olczyk

| I posted a question to an emacs newsgroup recently along with some code.
| Someone came back saying that I should make my code more "functional".

When people make this "suggestion", it may be useful to think of
"functional" as the opposite of "dysfunctional". Some people want all
Lisps to be functional programming languages, but that is because they
think Scheme is a Lisp, as Scheme is certainly closer to the functional
programming style than Lisps. Emacs Lisp in particular lacks the feature
that it would make sense to call "functional": no lexical scope and no
closures, meaning: no support for currying, the feature most commonly
associated with functionaly programming style. Please note that only
fanatics would consider "functional style" valuable without reference to
a purpose, requirement, or specification. Stylistic complaints with no
real impact on readability or functionality should be ignored. I have
seen people rewrite clean, readable code into a complex mess simply
because it contained a "goto" that triggered some allergic reaction.

| The complaint, he fealt I used setf too much.

Ignore that. Lack of assignment to local variables is not the real
measure of a "functional" programming style, any more than lack of "goto"
makes code modular or whatever.

| To me it doesn't seem to make much of a difference.

It makes absolutely no difference. The close proximity of the binding
and the setting removes any and all ambiguity as to the purpose of the
code, and the only possible loss of value is that of "reasoning" about
the code, as if anyone could reason about Emacs Lisp code to begin with.

A vacuous gripe that code should be more "functional" is akin to the
vacuous gripe that someone should be more "polite": Someone has a problem
they fail to cope with on their own, so they ask you to do their work for
them. Just ignore such people; they have vastly different goals than
helping you write _correct_ code.

However, your code layout sucks. :) If you write Emacs Lisp code, use
M-( and M-) to open and close lists, use C-j to jump to the next line
with proper indentation. If you insist on writing code as if you were in
Word or something, at least use M-C-q when at the opening parenthesis of
the top-level form. If you disagree with the way Emacs indents your
code, you are simply wrong. Indentation is the law in Lisp. I sometimes
think compilers should gripe about indentation vs structure mismatches,
but that would only really make sense if the editor was part of the whole
Lisp environment. In Emacs it is...

///
--

Kenny Tilton

unread,
Jan 13, 2002, 8:56:52 PM1/13/02
to

Coby Beck wrote:
>
> "Kenny Tilton" <kti...@nyc.rr.com> wrote in message
> news:3C420DE4...@nyc.rr.com...
> > Genius is in the details.
>
> I thought that where the devil was... ;-)

That's the beauty of details, it's /all/ in there:

http://www.vitalie-manufacturing.com/Cover.htm

"devil" is the winner, tho, if you go by Google. Of course Google does
not use Lisp, so you have to consider the source.

kenny
clinisys

Thomas F. Burdick

unread,
Jan 13, 2002, 9:50:51 PM1/13/02
to
Erik Naggum <er...@naggum.net> writes:

> * Thaddeus L Olczyk
> | I posted a question to an emacs newsgroup recently along with some code.
> | Someone came back saying that I should make my code more "functional".
>
> When people make this "suggestion", it may be useful to think of
> "functional" as the opposite of "dysfunctional". Some people want all
> Lisps to be functional programming languages, but that is because they
> think Scheme is a Lisp, as Scheme is certainly closer to the functional
> programming style than Lisps.

And Emacs Lisp in particular is *really* far from functional style, in
practice. All the operations on buffers make it more like OOP than
anything else, IMHO.

> Emacs Lisp in particular lacks the feature that it would make
> sense to call "functional": no lexical scope and no closures,
> meaning: no support for currying, the feature most commonly
> associated with functionaly programming style.

It doesn't have lexical scoping, but the cl package can fake it:

ELISP> (require 'cl)
cl
ELISP> (lexical-let ((count 0))
(defun reset-counter ()
(setf count 0))
(defun count ()
(incf count)))
count
ELISP> (count)
1
ELISP> (count)
2
ELISP> (reset-counter)
0
ELISP> (count)
1

In fact, it fakes it so well, only the docstring can tell :) I mention
this just because it's an underused feature of elisp, and this is
probably a group of people that can make good use of it. It's
especially good when writing callbacks for widgets form the widget
package, even if the documentation doesn't show it that way in the
examples.

> I have seen people rewrite clean, readable code into a complex
> mess simply because it contained a "goto" that triggered some
> allergic reaction.

Indeed, that's one feature horribly missing from elisp. Given the
quality of most elisp, maybe it's a good thing on the average, but
when you want to use goto, it sucks not to have it.

--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'

Software Scavenger

unread,
Jan 13, 2002, 10:56:01 PM1/13/02
to
Barry Margolin <bar...@genuity.net> wrote in message news:<Cdn08.10$rW4.170054@burlma1-snr2>...

> (defun foo ()
> (prog1
> (if (some-cond) retval1 retval2)
> (do-some-processing)))
>
> However, I'm not a big fan of using PROG1. It's mainly a historical
> remnant from the days before LET, so creating local variables wasn't as
> convenient.

Prog1 seems useful to me, and seems to make the code clearer than any
of the other styles. It tells the person reading the code that there
is some special reason for calculating the return value before doing
the other processing. Regardless of the process by which CL evolved
and got standardized, it's now what we have, so we might as well make
full use of it.

Tim Moore

unread,
Jan 14, 2002, 3:02:06 AM1/14/02
to
In article <a6789134.02011...@posting.google.com>, "Software
Scavenger" <cubic...@mailandnews.com> wrote:


> Barry Margolin <bar...@genuity.net> wrote in message
> news:<Cdn08.10$rW4.170054@burlma1-snr2>...

>> However, I'm not a big fan of using PROG1. It's mainly a historical
>> remnant from the days before LET, so creating local variables wasn't as
>> convenient.

> Prog1 seems useful to me, and seems to make the code clearer than any of
> the other styles. It tells the person reading the code that there is
> some special reason for calculating the return value before doing the
> other processing. Regardless of the process by which CL evolved and got
> standardized, it's now what we have, so we might as well make full use
> of it.

And don't forget its relative multiple-value-prog1, which is very useful
and a pain to duplicate using other functions and forms.

Tim

Steven M. Haflich

unread,
Jan 13, 2002, 11:50:31 PM1/13/02
to

Tim Bradshaw wrote:

> I think that masses of SETFs are kind of a bad sign, although the kind
> of extreme counter example of code like:

I agree that masses of setfs may be a bad sign, but I think this
misses less obvious ways to abuse lexical variables. The valid
concern everyone has with coding style has a lot less to do with
the writing of the code than it does with the readability (and
maintainability) of the code. So how do lexical variables impose
a cost on readability? The intrinsic cost may be small, but the
cost increases with the scope of the variable.

Good coding practice is to make the scope of a variable binding
as small as reasonably possible. At the point that a binding is
establish, the human reader wants to understand the purpose and
use of that variable. If the scope is small, the eye can take in
all references to the variable. If the scope is large, the
reader must search to make sure all references are seen and
understood.

There are some variables whose necessary scope is large, of
course, but it is important that a single variable not be
used to hold disconnected values. We've all seen code like this

(let (temp)
... ; lots of code here
(setq temp ...) ; [1]
...
(foo ... temp ...) ; [2]
... ; lots more code here
(setq temp ...) ; [3]
...
(bar temp) ; [4]
... ; lots of code here
)

although it is far more common in FORTRAN than Lisp. The value
of temp assigned in [1] and used in [2] becomes dead after [2].
An entirely new, unrelated value is assigned in [3] and used in
[4], but if the size of the surrounding code (elided here) is
large, the human eye cannot readily discern at [2] that the
value is dead. A text editor can be helpful in searching out
any further references (assuming the original coder didn't use a
single character variable name), but this slows down the
acquisition of the code and incorrect analysis is a open sore
inviting infection by pernicious bugs. IMO, such code is much
clearer written as:

... ; [A]
(let ((temp ...))
...
(foo ... temp ...)
...) ; [B]
... ; [C]
(let ((temp ...))
...
(bar temp)
)
... ; [D]

With this equivalent expression, the reader of the code wanting
to understand each of the temp variables needs look no further
than the end of the binding to be sure he has seen all
references. The mass of code in [A], [B], [C], and [D] is
visually demarcated as irrelevant to the variables.

This is one of the things I don't like when I have to read
C++ or Java code. Although both languages feature lexical
bindings roughly equivalent to Lisp lexical variables, and
while these are often used correctly (with properly limited)
scope, variables need _not_ be bound at the head of the
binding contour in which they appear. A "char * p;" can appear
anywhere within a lexical block, and then a human reader
notices a reference to p in the middle of a mass of code, he
can't simply scan upward to the start of each surrounding
block to find the innermost surrounding binding; he must scan
back through all the code. And he can't even be sure that the
first binding found scanning backwards is the appropriate one
because that one might be in some other lexical branch not
visible to the code in question.

Top-level "members" are scoped across an entire source file.
This also complicates the task of finding all mentions of the
member by eye. It isn't always easy to find the declaration
of the member, since it isn't guaranteed to be at a certain
particular place in the source and may be interspersed with
other definitions. (To be fair, programming untilities like
tags files and cross-reference utilities fill much of this
need.)

The limited scope of function members in these langauges, on
the other hand, is laudable, since if the code is written
with the correct access restrictions it is possible to know
that all the places where an internal function could be called
are in the file where it is defined, or in certain related
class files. The CL package system is nowhere near that
expressive.

Fredrik Sandstrom

unread,
Jan 18, 2002, 5:22:28 AM1/18/02
to
In article <a1u39u$7kb$0...@216.39.145.192>, Tim Moore wrote:
>And don't forget its relative multiple-value-prog1, which is very useful
>and a pain to duplicate using other functions and forms.

How so? What's wrong with this:

(defmacro my-multiple-value-prog1 (form1 &rest forms)
(let ((retvals (gensym)))
`(let ((,retvals (multiple-value-list ,form1)))
,@forms
(apply #'values ,retvals))))

OK, you might want to avoid consing up a list, but are there other
problems?

--
- Fredrik Sandstrom fre...@infa.abo.fi http://infa.abo.fi/~fredrik -
Computer Science at Abo Akademi University --

Barry Margolin

unread,
Jan 18, 2002, 11:22:20 AM1/18/02
to
In article <slrna4ftn6....@deepthought.coax>,

Fredrik Sandstrom <fre...@infa.abo.fi> wrote:
>In article <a1u39u$7kb$0...@216.39.145.192>, Tim Moore wrote:
>>And don't forget its relative multiple-value-prog1, which is very useful
>>and a pain to duplicate using other functions and forms.
>
>How so? What's wrong with this:
>
>(defmacro my-multiple-value-prog1 (form1 &rest forms)
> (let ((retvals (gensym)))
> `(let ((,retvals (multiple-value-list ,form1)))
> ,@forms
> (apply #'values ,retvals))))
>
>OK, you might want to avoid consing up a list, but are there other
>problems?

Perhaps he meant "error-prone", since many programmers are likely to forget
to handle multiple values (as the OP did).

Then again, these programmers would probably forget to use M-F-PROG1.

BTW, (apply #'values ,retvals) == (values-list ,retvals).

0 new messages