thisfn is no more

106 views
Skip to first unread message

Rich Hickey

unread,
Jan 31, 2008, 2:25:15 PM1/31/08
to Clojure
I've removed thisfn from Clojure.

The original idea was to allow for recursive anonymous functions and
avoid another let construct (letrec/labels) for recursive local
functions.

In dropping thisfn I've preserved the latter while losing the former,
which was not used once in boot.clj or elsewhere, and is not possible
in any other Lisp AFAIK (Y-combinator aside).

The reason it had to go has to do with hygiene - thisfn was an
injected name, and thus not under the control of the user, and it
would be injected in every fn, even ones emitted by macros. In
particular, it was a problem for lazy-cons, which emits a fn. If the
rest expression you supplied contained a reference to thisfn, it
resolved to the generated fn and not the lexical one surrounding your
call. I've had to dance around that a few times but I dreaded having
to explain to someone else. Fortunately I didn't have to, and now I
never will.

Another shortcoming with thisfn was that nested local fns couldn't
recur to their parents without letting a name be bound to thisfn on
the way.

The replacement is fairly simple - you can refer to the fn by the name
to which it is bound. For defns, that's the var name, and always
worked. Let-bound fns required a small enhancement to let:

If a let-bound local is initialized with a fn expression, that
local binding is visible to the function definition.

In practice, this means that let, when binding a local to a fn, acts
somewhat like letrec/labels in allowing the function to refer to
itself, but unlike them in that mutually-recursive local fns are still
not possible.

There are some subtleties that I feel are unlikely to occur in
practice. This works:

(defn foo [] (let [bar (fn [] bar)] (bar))) ;local bar returns itself

but this won't compile unless there is a visible bar in the enclosing
scope:

(defn foo [] (let [bar (identity (fn [] bar))] (bar))) ;local bar
returns enclosing bar, or fails to compile if none

-> java.lang.Exception: Unable to resolve symbol: bar in this context

The bottom line being that the fn in the second example is an
anonymous fn.

It also means that let-bound fns cannot refer to enclosing fns of the
same name (a la CL flet), but if that's really what you want to do the
second example shows how.

An example of where this helps is cycle (in boot.clj), which
previously was broken into 2 top-level functions to avoid the thisfn/
lazy-cons issue, now becomes this:

(defn cycle [coll]
(when (seq coll)
(let [rep (fn [xs]
(if xs
(lazy-cons (first xs) (rep (rest xs)))
(recur (seq coll))))]
(rep (seq coll)))))


I think this is a good solution, it leaves Clojure simple and removes
a blemish.

Feedback welcome,

Rich

John Cowan

unread,
Jan 31, 2008, 5:54:56 PM1/31/08
to clo...@googlegroups.com
On Jan 31, 2008 2:25 PM, Rich Hickey <richh...@gmail.com> wrote:

> I've removed thisfn from Clojure.

I support removing thisfn for the reasons you give, but I am very much
against interfering with the clean semantics of let. Sure as hell,
someone somewhere is going to use let to bind a variable to the result
of a macro that introduces a top-level fn under the covers and get
surprised by the result.

> The original idea was to allow for recursive anonymous functions and
> avoid another let construct (letrec/labels) for recursive local
> functions.
>
> In dropping thisfn I've preserved the latter while losing the former,
> which was not used once in boot.clj or elsewhere, and is not possible
> in any other Lisp AFAIK (Y-combinator aside).

If you don't want letrec, you can go back to Lisp 1.5's label, with
the syntax (label name (lambda arglist . body)) or the Scheme
extension rec, with the syntax (rec name arglist . body). But
"knotted" name binding needs its own construct one way or another:
letrec/labels is just the most general method.

> I think this is a good solution, it leaves Clojure simple and removes
> a blemish.

By being too simple, it introduces another blemish. Things should be
as simple as possible, but no simpler, as Einstein said.

--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures

Rich Hickey

unread,
Jan 31, 2008, 7:22:01 PM1/31/08
to Clojure


On Jan 31, 5:54 pm, "John Cowan" <johnwco...@gmail.com> wrote:
> On Jan 31, 2008 2:25 PM, Rich Hickey <richhic...@gmail.com> wrote:
>
> > I've removed thisfn from Clojure.
>
> I support removing thisfn for the reasons you give, but I am very much
> against interfering with the clean semantics of let. Sure as hell,
> someone somewhere is going to use let to bind a variable to the result
> of a macro that introduces a top-level fn under the covers and get
> surprised by the result.
>

Could you please give me an example of the scenario that concerns you?
(The expansion is sufficient, we can leave the macro out of it unless
it speaks to the surprise aspect).

> But
> "knotted" name binding needs its own construct one way or another:
> letrec/labels is just the most general method.
>
> By being too simple, it introduces another blemish. Things should be
> as simple as possible, but no simpler, as Einstein said.
>

I certainly don't want to do that, but you'll have to show me the
problem, please. Simply claiming it needs its own construct isn't
enough. I want to make sure we understand one another.

This is the behavior right now:

user=> (defn foo [] (let [bar (defn bar [] bar)] bar))
#<Var: user/foo>
user=> (def f (foo))
#<Var: user/f>
user=> f
#<Var: user/bar>
user=> (f)
user.foo$bar@95ef17
user=> bar
user.foo$bar@95ef17
user=> (bar)
user.foo$bar@95ef17

with which I see no problem.

Rich

Stuart Sierra

unread,
Jan 31, 2008, 10:27:58 PM1/31/08
to Clojure
On Jan 31, 2008 2:25 PM, Rich Hickey <richhic...@gmail.com> wrote:
> I've removed thisfn from Clojure.

> On Jan 31, 5:54 pm, "John Cowan" <johnwco...@gmail.com> wrote:
> > I support removing thisfn for the reasons you give, but I am very much
> > against interfering with the clean semantics of let. Sure as hell,
> > someone somewhere is going to use let to bind a variable to the result
> > of a macro that introduces a top-level fn under the covers and get
> > surprised by the result.

I won't even pretend to be an expert here, but would the following
work?
Instead of altering the semantics of "let", introduce a special
version of "fn" for recursive functions, like (recfn name [args]
body...), in which "name" is only defined within the body of the fn.
Then you could have anonymous recursive functions anywhere, not just
in "let" forms. But I don't know what the other implications might
be.
-Stuart

Rich Hickey

unread,
Feb 1, 2008, 9:21:46 AM2/1/08
to Clojure


On Jan 31, 10:27 pm, Stuart Sierra <the.stuart.sie...@gmail.com>
wrote:
Yes, I have plenty of options. Clojure used to have Lisp traditional
parallel let, let* and letrec. To your suggestion, I have thought of
and had designed rfn.

The most likely alternative is simply allowing a symbol to appear
between fn and [:

(fn foo [args] ...)

but it will lead to a lot of:

(let [foo (fn foo [args] ...)]

and people wondering why they have to say foo twice. Sure, if there
was letrec they could use that, but it has its own hand-waving issues
re: recursivity when binding non-lambdas. CL's labels avoids that by
allowing only function bindings, but results in a lot of tedious
nesting of let/labels.

I think one has to be careful with phrases like "the semantics of let"
as if there were inherent semantics for let. There aren't. Scheme/CL,
ML and Haskell all have different semantics, ML's being akin to let*
and Haskell's to letrec.

I like binding functions and non-functions in a single construct. I
think having a 'recursive' binding construct that can bind something
other than functions, which obviously can't be meaningfully
recursively defined (e.g. letrec), is as 'special' as what I have
proposed.

That said, I'll gladly change it if someone can demonstrate a problem
with it.

Rich

Rich Hickey

unread,
Feb 3, 2008, 10:11:47 AM2/3/08
to Clojure
On Feb 1, 9:21 am, Rich Hickey <richhic...@gmail.com> wrote:

> The most likely alternative is simply allowing a symbol to appear
> between fn and [:
>
> (fn foo [args] ...)
>

and, despite of a lack of counterexamples, that is what I have done
for now.

Now you can (optionally) put a symbol between fn and [ and that name
will be in scope in the body of the fn definition, where it will refer
to the fn itself. This will allow for recursive anonymous functions.
defn does this in its expansion.

> but it will lead to a lot of:
>
> (let [foo (fn foo [args] ...)]
>
> and people wondering why they have to say foo twice.

Interestingly, when changing those parts of Clojure that let a
recursive fn I always chose 'this' for the fn name, not the let name.
It seemed clearer, and isolated the let-bound name to the let, so if
it needed to be renamed I wouldn't have to touch the fn body.

I've also removed the injection of the let name into the fn
expression. Even if I revisit that in the future, I won't take away
the anonymous fn naming syntax.

Rich

John Cowan

unread,
Feb 3, 2008, 3:22:09 PM2/3/08
to clo...@googlegroups.com
On Feb 3, 2008 10:11 AM, Rich Hickey <richh...@gmail.com> wrote:

> Now you can (optionally) put a symbol between fn and [ and that name
> will be in scope in the body of the fn definition, where it will refer
> to the fn itself.

+1

> Interestingly, when changing those parts of Clojure that let a
> recursive fn I always chose 'this' for the fn name, not the let name.
> It seemed clearer, and isolated the let-bound name to the let, so if
> it needed to be renamed I wouldn't have to touch the fn body.

That sounds like sensible advice that should go in the manual; it's
like the tendency of Scheme programmers to use "loop" as the name of a
named let.

> I've also removed the injection of the let name into the fn
> expression. Even if I revisit that in the future, I won't take away
> the anonymous fn naming syntax.

I apologize for not getting back to you with specific examples; I've
been too swamped to think them up. I was just speaking out of general
experience with "special cases", especially in Lispish languages where
it's not always obvious that you are invoking a special case because
it's concealed behind a macro.

Jonathan T

unread,
Feb 4, 2008, 10:54:36 AM2/4/08
to Clojure
On Feb 3, 10:11 am, Rich Hickey <richhic...@gmail.com> wrote:
> Interestingly, when changing those parts of Clojure that let a
> recursive fn I always chose 'this' for the fn name, not the let name.
> It seemed clearer, and isolated the let-bound name to the let, so if
> it needed to be renamed I wouldn't have to touch the fn body.

I have to say... that's very clever. I've run in to the (let [foo (fn
foo [args] ...)] pattern before when designing functional languages,
and along with all the different Lisp let-forms it seemed like a
kludge. The convention of using "this" as the recursive name makes a
lot of sense and fits well with the rest of Clojure.

I guess I just wanted to say, keep up the good work. :-) A lot of
design choices you've made with Clojure have been surprising, but they
seem to be for the best so far. The community would do well if more
people followed your lead.

Henk Boom

unread,
Feb 4, 2008, 11:48:25 AM2/4/08
to clo...@googlegroups.com
On 03/02/2008, Rich Hickey <richh...@gmail.com> wrote:
>
> On Feb 1, 9:21 am, Rich Hickey <richhic...@gmail.com> wrote:
>
> > The most likely alternative is simply allowing a symbol to appear
> > between fn and [:
> >
> > (fn foo [args] ...)
> >
>
> and, despite of a lack of counterexamples, that is what I have done
> for now.

If anyone's curious, it's easy to implement named let with this
addition, I find it clearer than ((fn this [arg-names] ...)
arg-inits):

(defmacro nlet [name args & body]
(let [arg-names (take-nth 2 args)
arg-inits (and args (take-nth 2 (rest args)))]
`((fn ~name [~@arg-names] ~@body) ~@arg-inits)))

You just have to be careful of the lack of TCO. Is there a way to
recur from inside multiple function boundaries?

Henk

Rich Hickey

unread,
Feb 4, 2008, 12:53:45 PM2/4/08
to Clojure


On Feb 4, 11:48 am, "Henk Boom" <lunarcri...@gmail.com> wrote:
You've just walked down the path leading to why ((fn this [arg-
names] ...)
arg-inits) is not idiomatic Clojure (not that it never happens :) and
there is no named let - no TCO means this can be a dangerous pattern
to adopt.

No, no recur across function boundaries, nor even from nested loops,
although as discussed before the latter might be addressed by named
loops at some point.

Rich
Reply all
Reply to author
Forward
0 new messages