* Re: Elisp programming style
2011-10-28 14:09 ` Thorsten
@ 2011-10-28 14:31 ` Nick Dokos
2011-10-28 15:59 ` Thorsten
2011-10-29 2:13 ` Eric Abrahamsen
2011-10-28 14:34 ` Tassilo Horn
` (2 subsequent siblings)
3 siblings, 2 replies; 14+ messages in thread
From: Nick Dokos @ 2011-10-28 14:31 UTC (permalink / raw)
To: Thorsten; +Cc: nicholas.dokos, emacs-orgmode
Thorsten <quintfall@googlemail.com> wrote:
> Štěpán Němec <stepnem@gmail.com> writes:
>
> > On Thu, 27 Oct 2011 20:03:22 +0200
> > Thorsten wrote:
> >
> >> Hi List, doing some Elisp programming (in an orgmode context)
> >> recently, the following question with regards to the 'accepted
> >> programming style' for Elisp concerned me:
> >>
> >> How independent and self-sustained should helper functions be?
> >>
> >> I found some redundancy in elisp code, e.g. several (main and helper)
> >> functions that do exactly the same thing to extract the same specific
> >> args out of an argument list. My first reaction was, to factor out
> >> this extraction into the main function, and then call the helper
> >> functions from inside a (let ...) environment where the extracted
> >> args are stored in a local variable.
> >>
> >> But then I recognised, that the helper functions cannot be called
> >> independently anymore, but only make sense when called from this one
> >> main function with its local bindings.
> >>
> >> Is there a kind of convention in a case like this? Like: "Make every
> >> function, even a helper function, independent, and don't care about
> >> redundancy"? Just being curious
> >
> > Too bad you didn't give any concrete examples.
>
> The problem can be described easily:
>
> problem-specific helper-funcions (some redundancy avoided)
> ,-----------------------------------------------------------
> | (defun main-function (args)
> | (let ((var (assoc :key1 args))) ; extracting var once
> | ...
> | (helper-function1 ...) ; inside let using var
> | (helper-function2 ...) ; inside let using var
> | ))
> |
> | (defun helper-function1 ()
> | ...
> | )
> |
> | (defun helper-function2 ()
> | ...
> | )
> `-----------------------------------------------------------
>
You probably need let* though.
> vs
>
> standalone helper-functions (but redundancy)
> ,-------------------------------------------------------------
> | (defun main-function (args)
> | (let ((value (assoc :key1 args)) ; extracting var 1st time
> | ...
> | )
> | (helper-function1 ...) ; outside let
> | (helper-function2 ...) ; outside let
> | )
> |
> | (defun helper-function1 (args)
> | (let ((var (assoc :key1 args))) ; extracting var 2nd time
> | ...
> | ))
> |
> | (defun helper-function2 (args)
> | (let ((var (assoc :key1 args))) ; extracting var 3rd time
> | ...
> | ))
> `-------------------------------------------------------------
>
>
> > In general, if the helper function is only used in a single place and
> > isn't likely to be more generally useful, there might be no reason for
> > it to be a standalone function at all (other than modular programming,
> > which is always a good idea IMO).
> >
> > OTOH, if the helper function _is_ standalone and used in multiple
> > places, then there is no "redundancy", so I'm really not sure what you
> > mean.
>
> Is there a policy? Lets say, an org-library author thinks his helper
> functions will be of no use for nobody else and designs them
> non-standalone. A few years later somebody wants to write a similar
> library and trys to reuse some of the old helper functions, to no avail.
>
No policy that I know of. It's very much a matter of taste, how complicated
the functions are, whether they perform something generic or something really
specific etc.
> Would that be considered bad style from the original author, or is that
> up to personal choice and not considered a problem?
>
Not a problem - you can't predict the future. You do the best you can
with your current knowledge. You can always refactor in the future:
that's one reason that having the source code around is important. When
you refactor, you may be cursing the original author for the "bad"
decicions, but c'est la vie: at least, you have chosen a system where
*you* can do that and not depend on a third party: they might not even
be alive, let alone willing to do what you want.
Nick
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:31 ` Nick Dokos
@ 2011-10-28 15:59 ` Thorsten
2011-10-29 2:13 ` Eric Abrahamsen
1 sibling, 0 replies; 14+ messages in thread
From: Thorsten @ 2011-10-28 15:59 UTC (permalink / raw)
To: emacs-orgmode
Nick Dokos <nicholas.dokos@hp.com> writes:
> Not a problem - you can't predict the future. You do the best you can
> with your current knowledge. You can always refactor in the future:
> that's one reason that having the source code around is important. When
> you refactor, you may be cursing the original author for the "bad"
> decicions, but c'est la vie: at least, you have chosen a system where
> *you* can do that and not depend on a third party: they might not even
> be alive, let alone willing to do what you want.
Thats really great about Emacs lisp - everything is so easily
accessible, it seems like no big deal to change something. I still have
to get rid of the reflex to factor out every duplication of code that I
see. Lisp is not Java...
cheers
--
Thorsten
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:31 ` Nick Dokos
2011-10-28 15:59 ` Thorsten
@ 2011-10-29 2:13 ` Eric Abrahamsen
1 sibling, 0 replies; 14+ messages in thread
From: Eric Abrahamsen @ 2011-10-29 2:13 UTC (permalink / raw)
To: emacs-orgmode
[...]
>> Would that be considered bad style from the original author, or is that
>> up to personal choice and not considered a problem?
>>
>
> Not a problem - you can't predict the future. You do the best you can
> with your current knowledge. You can always refactor in the future:
> that's one reason that having the source code around is important. When
> you refactor, you may be cursing the original author for the "bad"
> decicions, but c'est la vie: at least, you have chosen a system where
> *you* can do that and not depend on a third party: they might not even
> be alive, let alone willing to do what you want.
I think this is probably the key. As a non-professional programmer who
has gone on safari through the org-mode code (though this obviously
applies to a lot of elisp packages), I think incremental growth is
responsible for a lot of oddities. Refactoring is possible in theory,
but for fast-growing packages it never becomes a priority, and my guess
is by this point there are parts of org where even Carsten would tread
very lightly. It's not a disaster, but it does lead to regression bugs,
which I think we're seeing.
Entropy is contravened, however: witness the push towards an org test
suite, and the production of a generic exporter in org…
Slightly OT, sorry,
Eric
--
GNU Emacs 24.0.90.1 (i686-pc-linux-gnu, GTK+ Version 2.24.4)
of 2011-10-06 on pellet
Org-mode version 7.7 (release_7.7.466.ga5cb)
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:09 ` Thorsten
2011-10-28 14:31 ` Nick Dokos
@ 2011-10-28 14:34 ` Tassilo Horn
2011-10-28 14:40 ` Eric Schulte
2011-10-28 16:04 ` Thorsten
2011-10-28 14:35 ` Eric Schulte
2011-10-28 17:43 ` Tom Prince
3 siblings, 2 replies; 14+ messages in thread
From: Tassilo Horn @ 2011-10-28 14:34 UTC (permalink / raw)
To: emacs-orgmode
Thorsten <quintfall@googlemail.com> writes:
>> Too bad you didn't give any concrete examples.
>
> The problem can be described easily:
>
> problem-specific helper-funcions (some redundancy avoided)
> ,-----------------------------------------------------------
> | (defun main-function (args)
> | (let ((var (assoc :key1 args))) ; extracting var once
> | ...
> | (helper-function1 ...) ; inside let using var
> | (helper-function2 ...) ; inside let using var
> | ))
> |
> | (defun helper-function1 ()
> | ...
> | )
> |
> | (defun helper-function2 ()
> | ...
> | )
> `-----------------------------------------------------------
>
> vs
>
> standalone helper-functions (but redundancy)
> ,-------------------------------------------------------------
> | (defun main-function (args)
> | (let ((value (assoc :key1 args)) ; extracting var 1st time
> | ...
> | )
> | (helper-function1 ...) ; outside let
> | (helper-function2 ...) ; outside let
> | )
> |
> | (defun helper-function1 (args)
> | (let ((var (assoc :key1 args))) ; extracting var 2nd time
> | ...
> | ))
> |
> | (defun helper-function2 (args)
> | (let ((var (assoc :key1 args))) ; extracting var 3rd time
> | ...
> | ))
> `-------------------------------------------------------------
That's still very vague. If `args' is some structure that has some
meaning and its parts belong together, say, an org entry, then it makes
sense to have the helper functions defined on that structure in order to
provide a consistent interface throughout the library. And I wouldn't
tell multiple similar let-bindings duplicate code.
But again, that's only my very abstract point of view. Given a concrete
example, my opinion might be completely upside-down. ;-)
Bye,
Tassilo
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:34 ` Tassilo Horn
@ 2011-10-28 14:40 ` Eric Schulte
2011-10-28 16:04 ` Thorsten
1 sibling, 0 replies; 14+ messages in thread
From: Eric Schulte @ 2011-10-28 14:40 UTC (permalink / raw)
To: Tassilo Horn; +Cc: emacs-orgmode
Tassilo Horn <tassilo@member.fsf.org> writes:
> Thorsten <quintfall@googlemail.com> writes:
>
>>> Too bad you didn't give any concrete examples.
>>
>> The problem can be described easily:
>>
>> problem-specific helper-funcions (some redundancy avoided)
>> ,-----------------------------------------------------------
>> | (defun main-function (args)
>> | (let ((var (assoc :key1 args))) ; extracting var once
>> | ...
>> | (helper-function1 ...) ; inside let using var
>> | (helper-function2 ...) ; inside let using var
>> | ))
>> |
>> | (defun helper-function1 ()
>> | ...
>> | )
>> |
>> | (defun helper-function2 ()
>> | ...
>> | )
>> `-----------------------------------------------------------
>>
>> vs
>>
>> standalone helper-functions (but redundancy)
>> ,-------------------------------------------------------------
>> | (defun main-function (args)
>> | (let ((value (assoc :key1 args)) ; extracting var 1st time
>> | ...
>> | )
>> | (helper-function1 ...) ; outside let
>> | (helper-function2 ...) ; outside let
>> | )
>> |
>> | (defun helper-function1 (args)
>> | (let ((var (assoc :key1 args))) ; extracting var 2nd time
>> | ...
>> | ))
>> |
>> | (defun helper-function2 (args)
>> | (let ((var (assoc :key1 args))) ; extracting var 3rd time
>> | ...
>> | ))
>> `-------------------------------------------------------------
>
> That's still very vague. If `args' is some structure that has some
> meaning and its parts belong together, say, an org entry, then it makes
> sense to have the helper functions defined on that structure in order to
> provide a consistent interface throughout the library. And I wouldn't
> tell multiple similar let-bindings duplicate code.
>
To clarify my earlier reply, I would agree with Tassilo above. If args
is a global variable (declared with defvar or defconst) then using it
without accepting it as an argument is not a problem (and the emacs-lisp
compiler will not complain).
Best -- Eric
--
Eric Schulte
http://cs.unm.edu/~eschulte/
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:34 ` Tassilo Horn
2011-10-28 14:40 ` Eric Schulte
@ 2011-10-28 16:04 ` Thorsten
1 sibling, 0 replies; 14+ messages in thread
From: Thorsten @ 2011-10-28 16:04 UTC (permalink / raw)
To: emacs-orgmode
Tassilo Horn <tassilo@member.fsf.org> writes:
> And I wouldn't tell multiple similar let-bindings duplicate code.
I have to get used to that, at first sight it looks like there is
redundancy everywhere. But its more from the Java perspective, from the
more functional perspective it makes sense.
cheers
--
Thorsten
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:09 ` Thorsten
2011-10-28 14:31 ` Nick Dokos
2011-10-28 14:34 ` Tassilo Horn
@ 2011-10-28 14:35 ` Eric Schulte
2011-10-28 15:52 ` Thorsten
2011-10-28 17:43 ` Tom Prince
3 siblings, 1 reply; 14+ messages in thread
From: Eric Schulte @ 2011-10-28 14:35 UTC (permalink / raw)
To: Thorsten; +Cc: emacs-orgmode
>
> The problem can be described easily:
>
> problem-specific helper-funcions (some redundancy avoided)
> ,-----------------------------------------------------------
> | (defun main-function (args)
> | (let ((var (assoc :key1 args))) ; extracting var once
> | ...
> | (helper-function1 ...) ; inside let using var
> | (helper-function2 ...) ; inside let using var
> | ))
> |
> | (defun helper-function1 ()
> | ...
> | )
> |
> | (defun helper-function2 ()
> | ...
> | )
> `-----------------------------------------------------------
>
> vs
>
> standalone helper-functions (but redundancy)
> ,-------------------------------------------------------------
> | (defun main-function (args)
> | (let ((value (assoc :key1 args)) ; extracting var 1st time
> | ...
> | )
> | (helper-function1 ...) ; outside let
> | (helper-function2 ...) ; outside let
> | )
> |
> | (defun helper-function1 (args)
> | (let ((var (assoc :key1 args))) ; extracting var 2nd time
> | ...
> | ))
> |
> | (defun helper-function2 (args)
> | (let ((var (assoc :key1 args))) ; extracting var 3rd time
> | ...
> | ))
> `-------------------------------------------------------------
>
Hmmm, this looks suspiciously like the case in some Babel functions :)
in which we originally has instances of the first style and then had to
manually transition to the second. IMO the first is very poor form, the
variables are technically "free variables" when defined in the helper,
and just through undocumented variable capture does the execution work
out correctly, this can lead to unpleasant bugs.
The means the helper may only be used when variables of certain names
are in scope.
If you do want to use a helper which uses variables in scope which
aren't passed as arguments (again just my opinion) you should defined
the helper function using `flet' *inside* of the main function and the
scope of the variables.
>
>
>> In general, if the helper function is only used in a single place and
>> isn't likely to be more generally useful, there might be no reason for
>> it to be a standalone function at all (other than modular programming,
>> which is always a good idea IMO).
>>
>> OTOH, if the helper function _is_ standalone and used in multiple
>> places, then there is no "redundancy", so I'm really not sure what you
>> mean.
>
> Is there a policy? Lets say, an org-library author thinks his helper
> functions will be of no use for nobody else and designs them
> non-standalone. A few years later somebody wants to write a similar
> library and trys to reuse some of the old helper functions, to no avail.
>
> Would that be considered bad style from the original author, or is that
> up to personal choice and not considered a problem?
>
I don't believe we have an official canon of such rules, but personally
I would consider this to be bad style. If the helper function is only
used in one main function then it should be defined using flet, if it is
used across multiple functions then the in-scope variables should be
passed as explicit arguments (preferably with names other than those
which it has in scope so you can be sure you are actually using the
arguments).
Finally, I believe the emacs-lisp compiler would complain about such
free variables.
Hope this helps, Best -- Eric
>
> cheers
--
Eric Schulte
http://cs.unm.edu/~eschulte/
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:35 ` Eric Schulte
@ 2011-10-28 15:52 ` Thorsten
0 siblings, 0 replies; 14+ messages in thread
From: Thorsten @ 2011-10-28 15:52 UTC (permalink / raw)
To: emacs-orgmode
Eric Schulte <schulte.eric@gmail.com> writes:
Hi Eric,
> Hmmm, this looks suspiciously like the case in some Babel functions :)
well ... ;)
> in which we originally has instances of the first style and then had to
> manually transition to the second. IMO the first is very poor form, the
> variables are technically "free variables" when defined in the helper,
> and just through undocumented variable capture does the execution work
> out correctly, this can lead to unpleasant bugs.
>
> The means the helper may only be used when variables of certain names
> are in scope.
>
> If you do want to use a helper which uses variables in scope which
> aren't passed as arguments (again just my opinion) you should defined
> the helper function using `flet' *inside* of the main function and the
> scope of the variables.
That sounds like an operational prescription to me, and I think will
follow this advice in the future.
>> Would that be considered bad style from the original author, or is that
>> up to personal choice and not considered a problem?
>>
>
> I don't believe we have an official canon of such rules, but personally
> I would consider this to be bad style. If the helper function is only
> used in one main function then it should be defined using flet, if it is
> used across multiple functions then the in-scope variables should be
> passed as explicit arguments (preferably with names other than those
> which it has in scope so you can be sure you are actually using the
> arguments).
>
> Finally, I believe the emacs-lisp compiler would complain about such
> free variables.
>
> Hope this helps, Best -- Eric
yes that helped, thanks
cheers
--
Thorsten
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 14:09 ` Thorsten
` (2 preceding siblings ...)
2011-10-28 14:35 ` Eric Schulte
@ 2011-10-28 17:43 ` Tom Prince
2011-10-28 18:05 ` Thorsten
3 siblings, 1 reply; 14+ messages in thread
From: Tom Prince @ 2011-10-28 17:43 UTC (permalink / raw)
To: Thorsten, emacs-orgmode
Perhaps
,-----------------------------------------------------------
| (defun main-function (args)
| (let ((var (assoc :key1 args))) ; extracting var once
| ...
| (helper-function1 ... var ...) ; inside let using var
| (helper-function2 ... var ...) ; inside let using var
| ))
|
| (defun helper-function1 (var')
| ...
| )
|
| (defun helper-function2 (var')
| ...
| )
`-----------------------------------------------------------
or
,-------------------------------------------------------------
| (defun get-key1 (args) (assoc :key1 args))
| (defun main-function (args)
| (let ((value (get-key1 args)) ; extracting var 1st time
| ...
| )
| (helper-function1 ...) ; outside let
| (helper-function2 ...) ; outside let
| )
|
| (defun helper-function1 (args)
| (let ((value (get-key1 args)) ; extracting var 2nd time
| ...
| ))
|
| (defun helper-function2 (args)
| (let ((value (get-key1 args)) ; extracting var 3rd time
| ...
| ))
`-------------------------------------------------------------
I likely wouldn't suggest the second, unless get-key1 was actually
something more complicated than your example.
Tom
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: Elisp programming style
2011-10-28 17:43 ` Tom Prince
@ 2011-10-28 18:05 ` Thorsten
2011-10-29 7:43 ` Tassilo Horn
0 siblings, 1 reply; 14+ messages in thread
From: Thorsten @ 2011-10-28 18:05 UTC (permalink / raw)
To: Tom Prince; +Cc: emacs-orgmode
Tom Prince <tom.prince@ualberta.net> writes:
> Perhaps
>
> ,-----------------------------------------------------------
> | (defun main-function (args)
> | (let ((var (assoc :key1 args))) ; extracting var once
> | ...
> | (helper-function1 ... var ...) ; inside let using var
> | (helper-function2 ... var ...) ; inside let using var
> | ))
> |
> | (defun helper-function1 (var')
> | ...
> | )
> |
> | (defun helper-function2 (var')
> | ...
> | )
> `-----------------------------------------------------------
>
> or
>
> ,-------------------------------------------------------------
> | (defun get-key1 (args) (assoc :key1 args))
> | (defun main-function (args)
> | (let ((value (get-key1 args)) ; extracting var 1st time
> | ...
> | )
> | (helper-function1 ...) ; outside let
> | (helper-function2 ...) ; outside let
> | )
> |
> | (defun helper-function1 (args)
> | (let ((value (get-key1 args)) ; extracting var 2nd time
> | ...
> | ))
> |
> | (defun helper-function2 (args)
> | (let ((value (get-key1 args)) ; extracting var 3rd time
> | ...
> | ))
> `-------------------------------------------------------------
>
>
> I likely wouldn't suggest the second, unless get-key1 was actually
> something more complicated than your example.
hmm ... I feel quite convinced now that having a standalone function is
worth a bit of code duplication, and I start do discover that the cl
package does have some nice functions (like flet).
cheers
--
Thorsten
^ permalink raw reply [flat|nested] 14+ messages in thread