defrecord with "inheritance"

2,130 views
Skip to first unread message

Warren Lynn

unread,
May 20, 2012, 1:22:55 PM5/20/12
to Clojure
So from what I read the philosophy of Clojure discourages inheritance
on concrete data types. However, maybe I am too entrenched in my OO
thinking, how do I define a new record type that includes all the data
members of another record type? I am thinking about the classic
Employee/Person example.

If I can define a record of Employee with Person's data members
included, even that is not true inheritance (as no protocols of
"Person" will be automatically extended to "Employee"), I need that
for function re-use (so a function working on Person will
automatically work on Employee because Employee is guaranteed to have
all the data members in Person).

Also, again, maybe I am too OO minded, is there a way inherit another
record type's protocol implementation? That seems to give the best
combination of both worlds (OO and functional), so you can either have
you own very customized combination of data type/protocols, or use the
very common OO pattern. Just like we have both the single-typed
dispatching (which is more OO like and covers a large portion of use
cases), and more advanced multimethods.

Thanks.

Vinzent

unread,
May 20, 2012, 1:56:03 PM5/20/12
to clo...@googlegroups.com
You can reuse methods by putting them in a map and then just merging it with the new methods:

(extend Employee
        AProtocol 
        (merge default-implementation {:new-function (fn ...)}))

The problem is that you can't reuse methods defined inline, i.e. you can't say "my record implements this protocol just like that other record".

воскресенье, 20 мая 2012 г., 23:22:55 UTC+6 пользователь Warren Lynn написал:

nicola...@gmail.com

unread,
May 20, 2012, 4:16:03 PM5/20/12
to clo...@googlegroups.com
> (extend Employee
>         AProtocol
>         (merge default-implementation {:new-function (fn ...)}))
>

But you lose a bit of performance with that...

Warren Lynn

unread,
May 20, 2012, 4:50:11 PM5/20/12
to Clojure
Thanks. That will work. But I wish things can get more concise and
elegant.

I like the Python idea of "make simple things easier and difficult
things possible". So I think the limited "inheritance" I mentioned can
make a large portion of use cases easier, without sacrificing any of
Clojure's more advanced features. Basically, I wish to have something
like:

1. (defrecord Employee [x y] :base Person)
so I can have all data fields in Person also included in Employee
2. (extend-type Employee
GetName :reuse Person)
so I simply reuse GetName implementation from Person

Maybe there is already something like that I am not aware of. But if
not, I really hope more people will concur and so it will get into the
language.

More broadly, I think the success of a language depends on two things:
1. Flexible so it is not just usable on trivial problems.
2. Provides/encourages good patterns so the flexibility won't get out
of control, especially on large projects.

Of course the challenge is how to balance these two. OO provides very
natural patterns for people to work with, so I think we should embrace
it as much as possible without sacrificing the unique flexibility/
power Clojure brings.

Kevin Downey

unread,
May 20, 2012, 4:51:40 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 10:22 AM, Warren Lynn <wrn....@gmail.com> wrote:
> So from what I read  the philosophy of Clojure discourages inheritance
> on concrete data types. However, maybe I am too entrenched in my OO
> thinking, how do I define a new record type that includes all the data
> members of another record type? I am thinking about the classic
> Employee/Person example.

Don't make data members. Use maps and multimethods. You don't need
types for this. You have data about a thing, keep it has data,
suddenly all the functions that work on data structures (select-keys,
update-in, clojure.set) all work on your data instead of having your
data locked away in a type.

> If I can define a record of Employee with Person's data members
> included, even that is not true inheritance (as no protocols of
> "Person" will be automatically extended to "Employee"), I need that
> for function re-use (so a function working on Person will
> automatically work on Employee because Employee is guaranteed to have
> all the data members in Person).
>
> Also, again, maybe I am too OO minded, is there a way inherit another
> record type's protocol implementation? That seems to give the best
> combination of both worlds (OO and functional), so you can either have
> you own very customized combination of data type/protocols, or use the
> very common OO pattern. Just like we have both the single-typed
> dispatching (which is more OO like and covers a large portion of use
> cases), and more advanced multimethods.
>
> Thanks.
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en



--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Kevin Downey

unread,
May 20, 2012, 4:55:44 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 1:50 PM, Warren Lynn <wrn....@gmail.com> wrote:
> Thanks. That will work. But I wish things can get more concise and
> elegant.
>
> I like the Python idea of "make simple things easier and difficult
> things possible". So I think the limited "inheritance" I mentioned can
> make a large portion of use cases easier, without sacrificing any of
> Clojure's more advanced features. Basically, I wish to have something
> like:
>
> 1. (defrecord Employee [x y] :base Person)
>     so I can have all data fields in Person also included in Employee
> 2. (extend-type Employee
>      GetName :reuse Person)
>    so I simply reuse GetName implementation from Person

you have no reason to give up the flexibility of maps and data for the
rigidness of types and an object graph.

{:employee? true} beats the above hands down.

Warren Lynn

unread,
May 20, 2012, 5:05:03 PM5/20/12
to Clojure
I don't quite understand where I introduced the rigidity. And what I
described is not a true inheritance as in traditional OO languages
(hence the quoted "inheritance"). It seems just some simple almost
syntax-level change (maybe some macros can solve it) will make
people's life a little bit easier.

On May 20, 4:55 pm, Kevin Downey <redc...@gmail.com> wrote:

Warren Lynn

unread,
May 20, 2012, 5:13:55 PM5/20/12
to Clojure
Maybe for Person/Employee example with very simple functions do not
need data types, but I don't think that is generally true (isn't
"defrecord" trying to patching that up?). When I say "need" I am not
saying you cannot achieve what you want to do without it (just as
assembly code can do anything), but things will get so flexible (hence
the lack of pattern) the language will be "powerful" only on paper
very soon. I am glad Clojure does not seem to be heading that
direction, but still I think there might be things we can improve on.

On May 20, 4:51 pm, Kevin Downey <redc...@gmail.com> wrote:

Kevin Downey

unread,
May 20, 2012, 5:15:15 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 2:05 PM, Warren Lynn <wrn....@gmail.com> wrote:
> I don't quite understand where I introduced the rigidity. And what I
> described is not a true inheritance as in traditional OO languages
> (hence the quoted "inheritance"). It seems just some simple almost
> syntax-level change (maybe some macros can solve it) will make
> people's life a little bit easier

"2. Functions delay binding; data structures induce binding. Moral:
Structure data late in the programming process."
-- http://www.cs.yale.edu/quotes.html

putting your data in specific types like Employee and Person instead
of just using maps is a kind of rigidity
>> And what is not good--

nicola...@gmail.com

unread,
May 20, 2012, 5:16:03 PM5/20/12
to clo...@googlegroups.com
I had wished such a feature all week.
You don't need it often, but when you need it, it is really annoying
to circumvent.
traits would really be useful.

Kevin Downey

unread,
May 20, 2012, 5:19:53 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 2:13 PM, Warren Lynn <wrn....@gmail.com> wrote:
> Maybe for Person/Employee example with very simple functions do not
> need data types, but I don't think that is generally true (isn't
> "defrecord" trying to patching that up?).  When I say "need" I am not
> saying you cannot achieve what you want to do without it (just as
> assembly code can do anything), but things will get so flexible (hence
> the lack of pattern) the language will be "powerful" only on paper
> very soon. I am glad Clojure does not seem to be heading that
> direction, but still I think there might be things we can improve on.

defrecord, deftype, and defprotocol provide extensible low level
abstractions like the kind Clojure is built on.

As a Clojure programmer you should only need them rarely.

As a beginner you should never use them.

Warren Lynn

unread,
May 20, 2012, 5:23:36 PM5/20/12
to Clojure
Well, I don't want to be a beginner for too long, :-)

On May 20, 5:19 pm, Kevin Downey <redc...@gmail.com> wrote:

Kevin Downey

unread,
May 20, 2012, 5:24:37 PM5/20/12
to clo...@googlegroups.com
If you find yourself wanting traits you are using defrecord, deftype,
and defprotocol too often.

If everything has the same implementation for X, should X really be
polymorphic on the type? shouldn't it just be another function?

If you find yourself using a polymorphic mechanism like protocols or
interfaces to give different types the same behavior then you need to
go back to the drawing board.

Kevin Downey

unread,
May 20, 2012, 5:26:32 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 2:23 PM, Warren Lynn <wrn....@gmail.com> wrote:
> Well, I don't want to be a beginner for too long, :-)

then "As a Clojure programmer you should only need them rarely."

Bill Caputo

unread,
May 20, 2012, 5:37:03 PM5/20/12
to Clojure

On May 20, 2012, at 4:23 PM, Warren Lynn wrote:
>> defrecord, deftype, and defprotocol provide extensible low level
>> abstractions like the kind Clojure is built on.
>>
>> As a Clojure programmer you should only need them rarely.
>>
>> As a beginner you should never use them.

> Well, I don't want to be a beginner for too long, :-)

I am not a clojure beginner (though far from feeling I know all there is to learn about it). I have been using clojure for almost a year; my team has rebuilt the central part of our system (which is relied on by just about every other team where I work) out of clojure and have had it in production for 6 months.

I've yet to even learn *how* to use defrecord, deftype & defprotocol.

IMO, If you're not doing a lot of java interop (i.e. where your clojure code is being consumed by java clients) you might never need them.

As someone who came from, C++, C# & Ruby (and a little Java) - i.e. OO - to clojure & FP, I *strongly* recommend that you take a project (preferably one that you aren't hanging your livelihood on, but trust me it's a real rush) and try *really* hard to solve your design problems just with maps, vectors and the other core data structures (I first tried this in ruby, btw - a great learning experience and gave me a strong appreciation for the optimizations that clojure provides to make such code practical).

IOW: pretend for a project that OO doesn't exist. When you're done, you'll have learned a lot, you'll still have what you know about OO, and when you're done you'll have lost nothing except your time and your perspective. You'll be doing yourself an enormous disservice if you simply try to map clojure onto your current way of working/thinking.

bill

Kevin Downey

unread,
May 20, 2012, 5:38:02 PM5/20/12
to clo...@googlegroups.com
why does defrecord and deftype exist? shouldn't deftype be sufficient?
Why is defrecord encouraged over deftype, with defype reserved for low
level bits?

it is because records can act like a generic map, which means data is
not hidden inside the type, which makes types created with defrecord
much more useful. How much more useful then is an ordinary map(Where
you can actually remove all the keys)?

I was the first person to introduce protocols and records into our
code base work, and I have colleagues who are even more ragingly
anti-protocols/types. Which I think is a little crazy, they are useful
and service a purpose. But even I am outraged by how quickly
multimethods (things of beauty, joy, and power) were abandoned by the
community once a shiny new toy (types/protocols) was available that if
you squinted at looked sort of like the object stuff.

Warren Lynn

unread,
May 20, 2012, 6:17:04 PM5/20/12
to Clojure
Thanks for the suggestion. I understand part of the joy (and pain) of
learning a new language is to change the way of thinking. So I
probably need to take on something no-trivial but also not
overwhelming to understand the issue or benefit better.

But eventually, a language cannot meet everybody's needs/tastes. In my
view, there are certain patterns that are just "natural" to most
people (not simply because they were taught like that in school), and
a language will be more productive for those people to have those
patterns (maybe with extra enhancements and enlightenment). I am sure
with maps and multimethods you have actually a superset of any OO
systems, and certain people find it much productive, but lacking
direct support of certain natural patterns will lose many capable but
non-genius programmers (which is nothing wrong if that is not part of
the language's objectives). Part of my learning here is to find out if
the language is right for me.

Alex Baranosky

unread,
May 20, 2012, 6:34:08 PM5/20/12
to clo...@googlegroups.com
For me learning Clojure was a real pain.  It was awkward and weird and felt wrong.  But really I was just trapped in an OO-only world and didn't know better.  I don't think there is s such a thing as what is "natural" in programming.  For loops are natural? -- psha.  Objects are natural? -- no way nature invents something so complicated.  

My point is that Clojure is a different perspective. You have to accept that and go with it, and then it might start to make sense.  It did for me.  To the point that I don't want to develop in another language.

So I suggest you take it to heart.  Put deftype, defrecord and defprotocol away and don't pull them back out for quite some time.  At the beginning, they are just a distraction from Clojure's core philosophy.  Focus on maps, vectors, sets, functions, multimethods and macros.

Alex

nicola...@gmail.com

unread,
May 20, 2012, 6:39:30 PM5/20/12
to clo...@googlegroups.com
> So I suggest you take it to heart.  Put deftype, defrecord and defprotocol
> away and don't pull them back out for quite some time.  At the beginning,
> they are just a distraction from Clojure's core philosophy.  Focus on maps,
> vectors, sets, functions, multimethods and macros.

But there are just some algorithms that you need to write in a low-level way.
(Implementing the data-structures built-in in Clojure would be such an example.)

And for this, it you are on the JVM, you want a way to build objects.
And then comes deftype. And after 2 hours, you realise that you would
like to have traits.
(It is quite rare to need them, but very useful when it is the case).

David Nolen

unread,
May 20, 2012, 7:31:05 PM5/20/12
to clo...@googlegroups.com
Nearly all of the data structures have been ported to ClojureScript. It's not clear to me that we needed traits at any point.

David

David Nolen

unread,
May 20, 2012, 7:34:46 PM5/20/12
to clo...@googlegroups.com
FWIW, the first thing I did when I encountered Clojure was built a Tiny CLOS like system with inheritance. I've since come to the conclusion it was a waste of time and Clojure offers an equally good set of tools.

After examining a few powerful paradigms, OO, FP, LP, etc I'm not sure what "natural" could possibly mean besides "familiar" which is a limited metric in my opinion.

David

Warren Lynn

unread,
May 20, 2012, 8:16:42 PM5/20/12
to Clojure

I agree "familiar" is often mixed with "natural". But nevertheless
that does not mean there is no such thing as "natural" thinking or
programming pattern. In a broader sense, the whole point of high-level
languages is to satisfy our need to express our ideas/logics in a more
natural way, hence the modular design, interface and etc. The
challenge of a language is how to be natural and powerful at the same
time, as human being's natural thinking often is not enough to model
the complexity of the world.

Alex Baranosky

unread,
May 20, 2012, 8:42:10 PM5/20/12
to clo...@googlegroups.com
I'd argue with you over whether that is the whole point of high-level languages.  I might say that high-level languages are there to make coding more efficient and effective.

Peter Buckley

unread,
May 20, 2012, 9:00:06 PM5/20/12
to clo...@googlegroups.com
-> high-level languages are there to make coding more efficient and effective

-> the whole point of high-level

languages is to satisfy our need to express our ideas/logics in a more
natural way

I'd argue that you're in violent agreement with each other :-)

If I can express my idea/logic in a more natural way, I would expect that to make my coding more efficient and effective.

I don't think there is such a thing as one "natural" thinking or programming pattern. I think what each of us regard as "natural" has a lot to do with different individuals' giftings, background, and experience.

I think it's valid to say that OO is a bad attempt to force a contrived and ill-fitting "physical" world model on computer programs, and it's totally unnatural. That's just a point of view on the "natural"-ness of the programming model for an individual.

Even if most programmers think that OO is an ideal model, that still doesn't mean it's the "natural" one.

From: Alex Baranosky <alexander...@gmail.com>
Date: Sun, 20 May 2012 17:42:10 -0700
Subject: Re: defrecord with "inheritance"

Warren Lynn

unread,
May 20, 2012, 9:13:39 PM5/20/12
to Clojure
That is certainly a philosophical question to which I cannot give any
proof to my view. In my view, as long as human beings are still
writing code, you cannot be truly efficient and effective without a
language that can let you express and organize your ideas in a natural
way (which itself is subject to debate). And as LISP looks unnatural
to many people on the surface, it actually makes a lot of things more
natural, hence I am very interest in it (I already did quite some
coding in Emacs Lisp and have some limited experience with Common
Lisp). For example, the "with-open", or a lot of other "with-xyz"
macros, filters out the noises and make people focus on the matter at
hand, which is natural to most people (you can argue it is also more
efficient, but I consider them the same or have a very strong
correlation). Further, the macro system can let you tailor the system
to fit your own particular "natural" style. But it cannot let you
tailor everything, so I am just hoping to gain as much as possible on
the "natural" part and lose as little as possible on the "unnatural"
part.

On May 20, 8:42 pm, Alex Baranosky <alexander.barano...@gmail.com>
wrote:

Softaddicts

unread,
May 20, 2012, 9:15:43 PM5/20/12
to clo...@googlegroups.com
Hi Warren,

"familiar" implies that your understanding relies on your previous experiences.
To get away from "familiar" concepts you need to think "out of the box".

How do you get rid of cabled thinking that brings you back always to the same
track ? Or should I say rut ?

It's an uneasy experience when you try to toss away your framed habits.

Having worked in Lisp, C and other languages, being stuck in Java for a number of years,
learning Clojure was a refreshing experience but even then:

A) immutability by default let me felt like a one-legged man for a couple of months,
were was this assignment thing I was used to ?

B) lazyness, this thing drove me nuts a few times, there were a few holes in the wall
of my office

Overall it took me a good three months to get over that uneasy feeling.
I was writing production code.... Not toying with the implementation.
That helped in a sense, there was a feeling of emergency.

Familiarity is relative, the thing to remember is to be critical of your own
thinking. If you let yourself trapped by your old habits, you may miss a whole
new (and potentially easier) way of doing things.

Clojure does not force fit you in a single approach to problems, it's up to
you to pick and choose. That by itself may increase the anxiety but it's worth it :)

Luc
--
Softaddicts<lprefo...@softaddicts.ca> sent by ibisMail from my ipad!

Warren Lynn

unread,
May 20, 2012, 9:35:44 PM5/20/12
to Clojure
I surely agree there is individual difference in thinking what is
natural. Mozart might thought writing a symphony was a very natural
thing to accomplish, which I would strongly disagree. But if the
language does not meet the natural thinking of the majority people
(which I do think exist), then the language won't be effective for
them. There is nothing wrong because the language may not target the
"majority" as the users. However it will be even better if the
language can make more people's coding more efficient without
sacrificing anybody's coding efficiency. I hope my original "feature
request" at the beginning falls into such category. After all, even an
niche or elite language will benefit from a broader participation and
users.


On May 20, 9:00 pm, "Peter Buckley" <buckmeist...@gmail.com> wrote:
> -> high-level languages are there to make coding more efficient and effective
>
> -> the whole point of high-level
> languages is to satisfy our need to express our ideas/logics in a more
> natural way
>
> I'd argue that you're in violent agreement with each other :-)
>
> If I can express my idea/logic in a more natural way, I would expect that to make my coding more efficient and effective.
>
> I don't think there is such a thing as one "natural" thinking or programming pattern. I think what each of us regard as "natural" has a lot to do with different individuals' giftings, background, and experience.
>
> I think it's valid to say that OO is a bad attempt to force a contrived and ill-fitting "physical" world model on computer programs, and it's totally unnatural. That's just a point of view on the "natural"-ness of the programming model for an individual.
>
> Even if most programmers think that OO is an ideal model, that still doesn't mean it's the "natural" one.
>
>
>
>
>
>
>
> -----Original Message-----
> From: Alex Baranosky <alexander.barano...@gmail.com>
>
> Sender: clo...@googlegroups.com
> Date: Sun, 20 May 2012 17:42:10
> To: <clo...@googlegroups.com>
> Reply-To: clo...@googlegroups.com
> Subject: Re: defrecord with "inheritance"
>
> I'd argue with you over whether that is the whole point of high-level
> languages.  I might say that high-level languages are there to make coding
> more efficient and effective.
>

David Nolen

unread,
May 20, 2012, 9:44:57 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 9:35 PM, Warren Lynn <wrn....@gmail.com> wrote:
I hope my original "feature
request" at the beginning falls into such category.

Don't hold your breath :) Look forward to seeing what you think further down the line.

David 

Mark Engelberg

unread,
May 20, 2012, 10:43:48 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 4:31 PM, David Nolen <dnolen...@gmail.com> wrote:
Nearly all of the data structures have been ported to ClojureScript. It's not clear to me that we needed traits at any point.

David



Until it has all been factored into protocols that make it easy for people to implement their own data structures that hook into Clojure's built-in functions, I'd say the jury's still out.  Currently, Clojure uses a mixture of interfaces and abstract base classes, and some things are very hard to derive and adapt.  For example, I've been waiting on changes for a couple of years that would allow me to hook priority maps into subseq.  Currently, subseq is implemented in terms of an interface that makes too many assumptions about how the data structure works.  I just don't think you can judge the success of inheritance-less records and protocols until that sort of work is done.

Warren Lynn

unread,
May 20, 2012, 10:47:03 PM5/20/12
to Clojure

Thanks a lot for the discussions/suggestions. Maybe the Clojure way,
whatever it means, will become natural to me someday. I still remember
how awkward it was for me to use Emacs (just because I could not find
another editor to do syntax highlighting for my particular file
format), and now I cannot do anything on the computer without it. You
never know. :-)

On May 20, 9:44 pm, David Nolen <dnolen.li...@gmail.com> wrote:

David Nolen

unread,
May 20, 2012, 11:07:07 PM5/20/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 10:43 PM, Mark Engelberg <mark.en...@gmail.com> wrote:
Until it has all been factored into protocols that make it easy for people to implement their own data structures that hook into Clojure's built-in functions, I'd say the jury's still out.  Currently, Clojure uses a mixture of interfaces and abstract base classes, and some things are very hard to derive and adapt.  For example, I've been waiting on changes for a couple of years that would allow me to hook priority maps into subseq.  Currently, subseq is implemented in terms of an interface that makes too many assumptions about how the data structure works.  I just don't think you can judge the success of inheritance-less records and protocols until that sort of work is done.

Have you actually looked at the data structure implementations in ClojureScript? The work is done. It's all protocols.

David 

nicola...@gmail.com

unread,
May 21, 2012, 4:53:32 AM5/21/12
to clo...@googlegroups.com
>
> Have you actually looked at the data structure implementations in
> ClojureScript? The work is done. It's all protocols.
>
Hi David,

I just have. This is a nice work. There is a lot of repetitions though.
For example:
- IEquiv
(-equiv [coll other] (equiv-sequential coll other))

- IHash
(-hash [coll] (caching-hash coll hash-coll __hash))
(where caching-hash has to be a macro)

- ICounted
(-count [coll] count)

- ILookup
(-lookup [coll k] (-nth coll k nil))
(-lookup [coll k not-found] (-nth coll k not-found))

All these appears multiple times in the file, each time for the same
underlying reason.
Each occurance is a missed opportunity for code reuse.
(There must be other repetitions, that just those I found in a 2 minutes search)
Add 4 more types of Vectors and you will have 4 more copies of those.
And, I am not sure of that, but I think that in Clojurescript, the
extend family has the same performance characteristics than deftype.
Which makes things easier.

Of course, deftype is sufficient for writing any class system, but you
have to repeat yourself.
And that is annoying.
I don't advocate inheritance, which I think is a wrong answer to a
good question, but
someone needs to think of a way to reuse code in deftype.

If you look at the big picture, multimethods are wonderful, protocols
are great, reify is amazing,
the extend family is top notch, but deftype lacks far behind in expressivity.
(It is a very low-level construct, which the other are not.)
And if deftype is in the language is because it is sometimes useful.
And if it is useful, it should be made a pleasure to use as is the
rest of the language.

My first idea would be to add a macro (deftype-something body) that
executes (or reduces) body at compile time
in order to build a data structure containing the fields and the
protocol/interface instances to be included in
the resulting deftype. And then build a small library of functions to
build classes. (extend-protocol, add-field, an so on...). It would
solve one of the main problem of deftype: neither FP nor macros can
help you much to build a type.

Best,

Nicolas.

Mark Engelberg

unread,
May 21, 2012, 5:39:10 AM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 1:53 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
I just have. This is a nice work. There is a lot of repetitions though.

Thanks Nicolas for putting together these examples.  This is exactly what I've been talking about.  Whenever someone talks about the issue of developing reusable partial implementation of protocols, the answer is always, "You can do that easily in Clojure by merging mappings."  But in practice, I haven't seen many examples of anyone actually doing that.  If people who are writing big systems (e.g., Clojurescript) aren't actually providing mergeable mappings, then writing new implementations for these protocols becomes a big copy-paste hackjob. 

I think part of the problem is that defrecord doesn't directly support using mappings of functions.  The construct that supports that is extend, and needs to be done outside of defrecord, possibly costing some efficiency.  This encourages people to copy-paste and not write reusable partial implementations. 

We need to figure out a way to make reusable partial implementations the norm.  Maybe the solution is a Clojure construct along the lines of:
(get-protocol-implementation <type> <protocol-or-interface>) gives you back a mapping of the functions used by that type to implement that protocol.
This would allow you to get at and reuse protocol implementations, even if the original author didn't think to separate out the mapping.

Also, a way to use such mappings inside of defrecord would also be a good idea, I think.

--Mark

Aaron Cohen

unread,
May 21, 2012, 9:12:28 AM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 4:53 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
>
> Have you actually looked at the data structure implementations in
> ClojureScript? The work is done. It's all protocols.
>
Hi David,

I just have. This is a nice work. There is a lot of repetitions though.

I actually like this repetition. When it's simple one-liners like this, I feel the explicitness is worth way more than any gains from re-use, particularly because re-use often becomes a game of trying to find which file defines the behavior you're interested in.

If the repetition were more substantial, the explicitness vs reuse tradeoff would eventually crossover though.

--Aaron

David Nolen

unread,
May 21, 2012, 9:55:16 AM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 4:53 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
> ClojureScript? The work is done. It's all protocols.
>
Hi David,

I just have. This is a nice work. There is a lot of repetitions though.
For example:
- IEquiv
 (-equiv [coll other] (equiv-sequential coll other))

-   IHash
 (-hash [coll] (caching-hash coll hash-coll __hash))
 (where caching-hash has to be a macro)

-  ICounted
 (-count [coll] count)

-  ILookup
 (-lookup [coll k] (-nth coll k nil))
 (-lookup [coll k not-found] (-nth coll k not-found))

And now any of those implementations can easily change at anytime without any external considerations. So what's the problem?

David 

David Nolen

unread,
May 21, 2012, 10:08:52 AM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 5:39 AM, Mark Engelberg <mark.en...@gmail.com> wrote:
On Mon, May 21, 2012 at 1:53 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
I just have. This is a nice work. There is a lot of repetitions though.

Thanks Nicolas for putting together these examples.  This is exactly what I've been talking about.  Whenever someone talks about the issue of developing reusable partial implementation of protocols, the answer is always, "You can do that easily in Clojure by merging mappings."  But in practice, I haven't seen many examples of anyone actually doing that.  If people who are writing big systems (e.g., Clojurescript) aren't actually providing mergeable mappings, then writing new implementations for these protocols becomes a big copy-paste hackjob. 

Have you looked at systems that use protocols extensively? The two I'm familiar with, core.logic and ClojureScript, do not constitute a "copy-paste hackjob". In fact I'd be surprised if the amount of copy-paste in either isn't less than 1% of the entire code base and a 1% where the redundancy is innocuous (IHash, IPrintable).
 
We need to figure out a way to make reusable partial implementations the norm.

Shared functions work today.

None of this to suggest that there aren't more "convenient" ways to share defaults. But I think the benefit of having type definitions that can be understood with an entirely local reading without having to search a type hierarchy or consider "defaults" should not be underestimated.

David 

David Nolen

unread,
May 21, 2012, 10:16:04 AM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 5:39 AM, Mark Engelberg <mark.en...@gmail.com> wrote:
It's possible to do very sophisticated types of code reuse for those that need it via delegation given we have the ability to specify extend-type Object/default.

David

nicola...@gmail.com

unread,
May 21, 2012, 11:11:15 AM5/21/12
to clo...@googlegroups.com
>
> And now any of those implementations can easily change at anytime without
> any external considerations. So what's the problem?

Well if you want to amend all of them at the same time, you have to
modify all of them.

On your advice of using extend-type: maybe I am wrong but I think it
has a performance cost.

I am not sure for the 1%, but the good metric is to look at the
percentage of deftypes, not the whole code base.
I think in a typical program, deftype already amount at less than 10%
of the code.

I wrote some code with a lot of different types implementing the same
interfaces and that are made
by composing different parts.
I ended up splitting aspects in different types and use composition
and "fake" protocols to implement
cross-cutting aspects. I would have loved to have traits to do that
instead: would have been easier, more performant
and more natural to read.

I could also have copied and pasted a lot, but it is asking for
problem if you modify things later.

David Nolen

unread,
May 21, 2012, 11:53:28 AM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 11:11 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
>
> And now any of those implementations can easily change at anytime without
> any external considerations. So what's the problem?

Well if you want to amend all of them at the same time, you have to
modify all of them.

As they are trivially the same, search+replace works really well in my experience.
 
On your advice of using extend-type: maybe I am wrong but I think it
has a performance cost.

An extra function dispatch or two? Not something I would lose sleep over for anything non-trivial. 
 
I am not sure for the 1%, but the good metric is to look at the
percentage of deftypes, not the whole code base.
I think in a typical program, deftype already amount at less than 10%
of the code.

Sure which just drives my point home even more.
 
I wrote some code with a lot of different types implementing the same
interfaces and that are made
by composing different parts.
I ended up splitting aspects in different types and use composition
and "fake" protocols to implement
cross-cutting aspects.

Without seeing your code hard to say anything about this.
 
I would have loved to have traits to do that
instead: would have been easier, more performant
and more natural to read.

Again without seeing your approach hard to tack any meaning to "more performant" or "more natural" to read.

I think ClojureScript is pretty compelling evidence to the soundness of a protocol oriented inheritance-less approach. We live with a little redundancy but we achieve a considerable amount of clarity and localized readability. We've compressed 30,000 (more?) lines of the Java+Clojure standard library into ~6500 lines of pure Clojure.

David

Softaddicts

unread,
May 21, 2012, 12:48:18 PM5/21/12
to clo...@googlegroups.com
The following is not about the merits of your request (or its foolishness :), it maybe a
taste issue.

If I look at productivity/efficiency, I cannot see the interest of sticking to the tools
and frameworks the majority is using presently. They are highly inefficients.

Doing things the same way they are already done and only changing the label
does not provide any gain whatsoever.

The heart of the problem is getting people to think differently about the things
they have been doing wrong for the last two decades.

I get regular feedback from people working in Javaish environments,
many criticize the methods and tools they use daily.
They feel they are not efficient and that it reduces their throughput significantly.

That uneasiness is a first important sign that something is wrong in the "natural"
thinking process of a significant chunk of developers (Java ones). Tools and
frameworks emerge from this "natural thinking".

I don't buy this idea of pleasing the majority but that maybe a personality trait.
It's not about selling Hygrade sausages, it's about questioning our "bad habits".

I got stuck into Java from back to 2001, my customers did not really questioned this
choice in all those years.
They never realized the jail they were getting into. Now they know they
have a problem, having tons of code lines to maintain and no easy escape route.

Clojure is flexible enough to allow you to address the same problem in different
ways within a short timeframe. You can then experiment other approaches
without having to switch to a different language/platform.

This is the door allowing you to escape the rut into which your mind may
be stuck. OO being one of them.

Personally, I stay away from deftype/defrecord most of the time.
I do however use protocols to implement "traits" of my own to standard Clojure
data structures. OO can easily be tossed away using protocols or high order
fns and maps. No need to carry that luggage. IMHO :)


Luc P.

Phil Hagelberg

unread,
May 21, 2012, 12:56:48 PM5/21/12
to clo...@googlegroups.com
On Sun, May 20, 2012 at 2:37 PM, Bill Caputo <logo...@gmail.com> wrote:
> I am not a clojure beginner (though far from feeling I know all there is to learn about it). I have been using clojure for almost a year; my team has rebuilt the central part of our system (which is relied on by just about every other team where I work) out of clojure and have had it in production for 6 months.
>
> I've yet to even learn *how* to use defrecord, deftype & defprotocol.

Same here--I can count on one hand the number of times I've wanted to
implement polymorphism in three and a half years. Every time
multimethods have worked great for the task. If you had polymorphism
in a tight loop they might not suffice, but the decision to abandon
multimethods should only be made after thorough benchmarking.

-Phil

nicola...@gmail.com

unread,
May 21, 2012, 1:12:37 PM5/21/12
to clo...@googlegroups.com
> Same here--I can count on one hand the number of times I've wanted to
> implement polymorphism in three and a half years. Every time
> multimethods have worked great for the task. If you had polymorphism
> in a tight loop they might not suffice, but the decision to abandon
> multimethods should only be made after thorough benchmarking.

I agree multimethods are great.
But when in a tight loop, you don't like them so much.

The point is not whether deftype is useful or not. It is in the
language so it must be useful, even it it is rarely.
The point is whether it is an expressive construct or not.
And it is not expressive enough to my taste.

David Nolen

unread,
May 21, 2012, 1:59:33 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 1:12 PM, nicola...@gmail.com <nicola...@gmail.com> wrote:
> Same here--I can count on one hand the number of times I've wanted to
> implement polymorphism in three and a half years. Every time
> multimethods have worked great for the task. If you had polymorphism
> in a tight loop they might not suffice, but the decision to abandon
> multimethods should only be made after thorough benchmarking.

The point is not whether deftype is useful or not. It is in the
language so it must be useful, even it it is rarely.
The point is whether it is an expressive construct or not.
And it is not expressive enough to my taste.

Why not build a trait system then if you want something more "expressive"? All the pieces are there. Basic benchmarking of delegation via extend-type Object on JVM Clojure shows that it's fast enough for many inner loops where the work is more substantial than arithmetic.

David

Mark Engelberg

unread,
May 21, 2012, 1:59:51 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 10:12 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
The point is not whether deftype is useful or not. It is in the
language so it must be useful, even it it is rarely.
The point is whether it is an expressive construct or not.
And it is not expressive enough to my taste.


I'd go further and say that an important benchmark for any new language construct designed to support modularity, composability, and reuse is the degree to which it can support reuse in contexts unforeseen by the original author of the code. 

The whole beauty of protocols, for example, is that unlike interfaces, you can graft it on to a type that wasn't originally designed to support it.  This is good, and a step forward from other languages.

On the other hand, defrecord/deftype encourage you to write your protocol implementation in a way that cannot be reused unless you very specifically plan for it (factoring the protocol implementation into a separate map) and use a coding style that (might?) be less efficient (i.e., adding the protocol implementation via extend rather than directly in defrecord).  This is not so good and potentially limits the value of protocols for developing complex systems.  There are a lot of bad things about inheritance, but the one good thing about it is that it provides a certain way to extend and modify the behavior of objects in ways that were not foreseen or planned for.  It would be nice if we could find a tasteful way to get that back with protocols without opening the entire inheritance can of worms.

David Nolen

unread,
May 21, 2012, 2:12:06 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 1:59 PM, Mark Engelberg <mark.en...@gmail.com> wrote:
On the other hand, defrecord/deftype encourage you to write your protocol implementation in a way that cannot be reused unless you very specifically plan for it (factoring the protocol implementation into a separate map) and use a coding style that (might?) be less efficient (i.e., adding the protocol implementation via extend rather than directly in defrecord).  This is not so good and potentially limits the value of protocols for developing complex systems.

One of most complex OO component systems that I'm aware of - Cocoa - doesn't rely on this kind of code reuse at all. Everything is done via delegation and protocols.

Games are some of the most complex OO systems being built today and the common wisdom is that building these systems on inheritance is a recipe for failure: http://www.gamasutra.com/blogs/MeganFox/20101208/88590/Game_Engines_101_The_EntityComponent_Model.php

Looks an awful like protocols, and if not protocols precisely - then extend-type Object + sugar will get you the rest of the way.

So "real world" complex systems avoid inheritance like the plague. So remind me again why it's desirable?

David

Raoul Duke

unread,
May 21, 2012, 2:12:27 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 10:59 AM, Mark Engelberg
<mark.en...@gmail.com> wrote:
> The whole beauty of protocols, for example, is that unlike interfaces, you
> can graft it on to a type that wasn't originally designed to support it.
> This is good, and a step forward from other languages.

i know protocols != structural typing, but just for that excerpt,
isn't structural/duck typing good enough as well?

David Nolen

unread,
May 21, 2012, 2:14:59 PM5/21/12
to clo...@googlegroups.com
How does duck typing support sensibly extending types you don't control?

Raoul Duke

unread,
May 21, 2012, 2:18:08 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 11:14 AM, David Nolen <dnolen...@gmail.com> wrote:
> How does duck typing support sensibly extending types you don't control?

ah, i missed the implicit part of the excerpt that was about extending
rather than just use. catching up.

thanks.

nicola...@gmail.com

unread,
May 21, 2012, 2:25:16 PM5/21/12
to clo...@googlegroups.com
>> On the other hand, defrecord/deftype encourage you to write your protocol
>> implementation in a way that cannot be reused unless you very specifically
>> plan for it (factoring the protocol implementation into a separate map) and
>> use a coding style that (might?) be less efficient (i.e., adding the
>> protocol implementation via extend rather than directly in defrecord).  This
>> is not so good and potentially limits the value of protocols for developing
>> complex systems.
>

Inheritance is bad because it ties code reuse to a (mostly)
non-significant hierarchy.
That is not a reason to prevent all code reuse from deftypes.

(It is not because OO gets it wrong that we should not try)

Warren Lynn

unread,
May 21, 2012, 5:21:49 PM5/21/12
to Clojure
Sometimes it seems to me"OO" and "inheritance" have become some kind
of taboos. I don't believe OO is all that wrong. To me Clojure seems
to have good potential to harness the good part of OO without carrying
into the bad parts. So I am hoping. :-)

On May 21, 2:25 pm, "nicolas.o...@gmail.com" <nicolas.o...@gmail.com>
wrote:

Mark Engelberg

unread,
May 21, 2012, 6:08:52 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 11:12 AM, David Nolen <dnolen...@gmail.com> wrote:
So "real world" complex systems avoid inheritance like the plague. So remind me again why it's desirable?

David



I never said I want inheritance.  I want convenient reuse of partial implementations.  Inheritance is one way to achieve it, but I'd like to think that Clojure can find a better way.  I believe the current mechanisms are insufficient.  Copy and paste is not good enough.  Mergeable maps are a good idea, but the current way of doing things steers people away from that solution and requires too much forethought to actively plan for reuse.  If I implement a protocol inside of defrecord, all that implementation is "locked inside" and no one can get at it or reuse it in other contexts.  This flies in the face of so many design principles of Clojure which generally eschew unnecessary encapsulation and privacy to make it easy to get at, reuse, and extend data and functions.

As I proposed in an earlier email, one brainstorm I have about this is:

(get-protocol-implementation <type> <protocol-or-interface>) gives you back a mapping of the functions used by that type to implement that protocol.
Also, we need a way to use mappings of functions inside of defrecord/deftype, not just extend, if that is technologically feasible.

I am genuinely interested in further brainstorms on the topic of how to encourage reuse of protocol implementations *without* inheritance.

David Nolen

unread,
May 21, 2012, 7:00:10 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 6:08 PM, Mark Engelberg <mark.en...@gmail.com> wrote:
As I proposed in an earlier email, one brainstorm I have about this is:

(get-protocol-implementation <type> <protocol-or-interface>) gives you back a mapping of the functions used by that type to implement that protocol.

I think there's some assumptions here about what level of granularity protocols are intended to work at right? Most of protocols in ClojureScript are 1 or 2 fns. If you need to provide partial implementations perhaps it's a sign the protocol design needs further work?

The only two protocols that involve specifying more than 2 fns is IWatchable (3) and MultiFn (5). It's not clear to me that they would benefit from partial specification.

Do you have a more specific example in mind?

David

kovas boguta

unread,
May 21, 2012, 7:23:42 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 6:08 PM, Mark Engelberg
<mark.en...@gmail.com> wrote:

> I never said I want inheritance.  I want convenient reuse of partial
> implementations.  Inheritance is one way to achieve it, but I'd like to
> think that Clojure can find a better way.

We also have macros.

You can always make your own

(defmytype TypeName ...)

and make compile to a deftype that rolls in the protocol
implementations you want to reuse.


I believe the current mechanisms
> are insufficient.  Copy and paste is not good enough.  Mergeable maps are a
> good idea, but the current way of doing things steers people away from that
> solution and requires too much forethought to actively plan for reuse.  If I
> implement a protocol inside of defrecord, all that implementation is "locked
> inside" and no one can get at it or reuse it in other contexts.  This flies
> in the face of so many design principles of Clojure which generally eschew
> unnecessary encapsulation and privacy to make it easy to get at, reuse, and
> extend data and functions.
>
> As I proposed in an earlier email, one brainstorm I have about this is:
>
> (get-protocol-implementation <type> <protocol-or-interface>) gives you back
> a mapping of the functions used by that type to implement that protocol.
> Also, we need a way to use mappings of functions inside of
> defrecord/deftype, not just extend, if that is technologically feasible.
>
> I am genuinely interested in further brainstorms on the topic of how to
> encourage reuse of protocol implementations *without* inheritance.
>

Mark Engelberg

unread,
May 21, 2012, 10:19:10 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 4:00 PM, David Nolen <dnolen...@gmail.com> wrote:
The only two protocols that involve specifying more than 2 fns is IWatchable (3) and MultiFn (5). It's not clear to me that they would benefit from partial specification.

I don't think there's enough body of code using protocols to really know what is going to turn out to be the natural size for protocols but even if we talk about "total" reuse instead of "partial" reuse, the issue is still the same -- the implementation of protocols is generally locked up with no easy way to reuse.  If I know I want to implement ILookup the same way it was implemented for some other data structure, I have to be able to see your code, and then copy and paste it.  For some things, it is obvious how to reimplement, but for some it is not.

When I implemented priority maps, I had a difficult time figuring out all the interfaces that needed to be implemented, and it was especially difficult to figure out the right way to implement the various notions of equivalence and equality.  Basically, I just wanted it to behave like the built-in maps.  I had to copy and paste, and shortly after I completed my implementation, a change was made to how these concepts were handled in the core.  My notion of equality became out-of-sync with that of built-in maps because I had no convenient way to say, "Do it the way regular maps do it."  I ended up having to rewrite that code. 

So there is a maintenance issue here too.  When Nicolas pointed out the many repetitions of something like the implementation of, say, ILookup, your response was that it would never be a big deal to just do a search-replace if you wanted to make a change.  But that assumes that the only copies occur in your own code.  When map equality changed, no one automatically did a search and replace in my code to make sure it would stay consistent.  This is why code reuse mechanisms that don't rely on copy/paste search/replace are valuable, especially in the core, where other people are trying hard to make structures that behave in a core-like way.


Mark Engelberg

unread,
May 21, 2012, 10:23:03 PM5/21/12
to clo...@googlegroups.com
On Mon, May 21, 2012 at 4:23 PM, kovas boguta <kovas....@gmail.com> wrote:
On Mon, May 21, 2012 at 6:08 PM, Mark Engelberg
<mark.en...@gmail.com> wrote:

> I never said I want inheritance.  I want convenient reuse of partial
> implementations.  Inheritance is one way to achieve it, but I'd like to
> think that Clojure can find a better way.

We also have macros.

You can always make your own

(defmytype TypeName ...)

and make compile to a  deftype that rolls in the protocol
implementations you want to reuse.


Yes, I can make a macro, but that won't do any good in terms of my reusing other people's code that have not been written in this manner.  If large bodies of code are written implementing protocols using the mechanism provided by defrecord, and then I can't conveniently reuse that implementation without seeing it and copying and pasting it, I still have a problem, macro or not.

meb

unread,
May 21, 2012, 11:56:53 PM5/21/12
to clo...@googlegroups.com
I think it's misleading to use inheritance to reduce code duplication.  Inheritance is about indicating function typing and creating typed contracts, both of which are fairly un-idiomatic in Clojure.

However, there is another way to prevent code duplication: use composition instead.  Instead of having the Employee class mirror the attributes of the Person (which if you really wanted, you could easily do via a "defperson" like macro that would slug on extra fields), why not have an Employee carry a Person as one of its attributes?  This approach is more similar to the Haskell/functional way of working with record types (I think Haskell's newtype operator works similarly under the covers).  There is also an analogue to the decorator pattern in Java.

In this case, you would specify a Personable protocol, which both Person and Employee implement; for all the basic operations, Employee would just defer to its internal person.  Example:

(defrecord Person [name birthday])

(defrecord Employee [title person])

(defprotocol Personable
  (name [this])
  (age [this))

(extend-protocol Personable
  Person
  (name [this] (:name this))
  (age [this] (date-dif (java.util.Date.) (:birthday this)) ;;date diff defined elsewhere
  Employee
  (name [this] (name (:person this))
  (age [this] (age (:person this)))

Arguably, if we were deferring to the current Java best practices and encapsulation, one should be creating interfaces of getters and setters rather than directly accessing instance variables anyway, making the extra protocol definition no more work than doing it correctly in modern Java.

Best,
Mark

On Sunday, May 20, 2012 10:22:55 AM UTC-7, Warren Lynn wrote:
So from what I read  the philosophy of Clojure discourages inheritance
on concrete data types. However, maybe I am too entrenched in my OO
thinking, how do I define a new record type that includes all the data
members of another record type? I am thinking about the classic
Employee/Person example.

If I can define a record of Employee with Person's data members
included, even that is not true inheritance (as no protocols of
"Person" will be automatically extended to "Employee"), I need that
for function re-use (so a function working on Person will
automatically work on Employee because Employee is guaranteed to have
all the data members in Person).

Also, again, maybe I am too OO minded, is there a way inherit another
record type's protocol implementation? That seems to give the best
combination of both worlds (OO and functional), so you can either have
you own very customized combination of data type/protocols, or use the
very common OO pattern. Just like we have both the single-typed
dispatching (which is more OO like and covers a large portion of use
cases), and more advanced multimethods.

Thanks.

Laurent PETIT

unread,
May 22, 2012, 1:02:47 AM5/22/12
to clo...@googlegroups.com
Le 22 mai 2012 à 04:19, Mark Engelberg <mark.en...@gmail.com> a écrit :

On Mon, May 21, 2012 at 4:00 PM, David Nolen <dnolen...@gmail.com> wrote:
The only two protocols that involve specifying more than 2 fns is IWatchable (3) and MultiFn (5). It's not clear to me that they would benefit from partial specification.

I don't think there's enough body of code using protocols to really know what is going to turn out to be the natural size for protocols but even if we talk about "total" reuse instead of "partial" reuse, the issue is still the same -- the implementation of protocols is generally locked up with no easy way to reuse.  If I know I want to implement ILookup the same way it was implemented for some other data structure, I have to be able to see your code, and then copy and paste it.  For some things, it is obvious how to reimplement, but for some it is not.

When I implemented priority maps, I had a difficult time figuring out all the interfaces that needed to be implemented, and it was especially difficult to figure out the right way to implement the various notions of equivalence and equality.  Basically, I just wanted it to behave like the built-in maps.  I had to copy and paste, and shortly after I completed my implementation, a change was made to how these concepts were handled in the core.  My notion of equality became out-of-sync with that of built-in maps because I had no convenient way to say, "Do it the way regular maps do it."  I ended up having to rewrite that code. 

Hello Mark,

Yes this is unfortunate. It would be interesting to see what would porting your priority map to clojurescript bring to the table, IMHO. Because clojure internals being a mix of java/interfaces and clojure certainly didn't help ?


So there is a maintenance issue here too.  When Nicolas pointed out the many repetitions of something like the implementation of, say, ILookup, your response was that it would never be a big deal to just do a search-replace if you wanted to make a change.  But that assumes that the only copies occur in your own code.  When map equality changed, no one automatically did a search and replace in my code to make sure it would stay consistent.  This is why code reuse mechanisms that don't rely on copy/paste search/replace are valuable, especially in the core, where other people are trying hard to make structures that behave in a core-like way.


Softaddicts

unread,
May 22, 2012, 1:09:02 AM5/22/12
to clo...@googlegroups.com
Interesting demonstration, except for one thing, defining getters is a waste of
time :)

When you need to define accessors, you start to run into inefficient use of
your time. It still a bearable workload in Java because of all this heavy tooling that allows
to select for which fields accessors will be generated.
Without getters, you would also expose mutable stuff to the universe.

However in Clojure the values are not mutable... Why bother defining
an accessor to a read-only value ?

A better way would be something like:

(defprotocol Personable
(person [this])
(age [this] )

(defprotocol CensusOperators
(age [this]))

(extend-protocol Personable
Person
(person [this] this)
Employee
(person [this] (:person this)))

(extend-protocol CensusOperators
Person
(age [this] (date-dif (java.util.Date.) (:birthday this)))))

Much more convenient to me. I can take anything and make it a person.
Even from a Clojure map just by extending it and returning a Person instance.
You expose the Person abstraction when available instead of hiding it.

Computed values like the age ? This is something that could be computed
for other entities like an animal.

So you can write (age (person xxxx)) or (age (animal xxxx)).

This is why I tell people to get out of their usual thinking process. Get lazy,
playing hide and seek was fun at pre-school age but in your code and data
structures ?

Luc P.
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Philip Potter

unread,
May 22, 2012, 2:03:18 AM5/22/12
to clo...@googlegroups.com


On May 22, 2012 7:09 AM, "Softaddicts" <lprefo...@softaddicts.ca> wrote:
> A better way would be something like:
>
> (defprotocol Personable
>    (person [this])
>     (age [this] )
>
> (defprotocol CensusOperators
>    (age [this]))
>
> (extend-protocol Personable
>    Person
>    (person [this] this)
>    Employee
>    (person [this] (:person this)))
>
> (extend-protocol CensusOperators
>     Person
>     (age [this] (date-dif (java.util.Date.) (:birthday this)))))
>
> Much more convenient to me. I can take anything and make it a person.

I really don't understand your example. Personable#age conflicts with CensusOperators#age if you define them in the same Namespace (and you'll get a warning telling you so) . If they're in separate namespaces , what's the point of Personable #age, given you never define an implementation?

Meikel Brandmeyer (kotarak)

unread,
May 22, 2012, 2:05:42 AM5/22/12
to clo...@googlegroups.com
Hi,


Am Dienstag, 22. Mai 2012 00:08:52 UTC+2 schrieb puzzler:

> Mergeable maps are a good idea, but the current way of doing things
> steers people away from that solution and requires too much
> forethought to actively plan for reuse.

I wonder why the current way of doing things “steers away” people
from using extend. extend is simply the default. Period. Implementing
things directly in a deftype or a defrecord should be the last resort
if performance requires it.

You loose not only the code reuse part when implementing things directly
in the types, you also welcome back name clashes.

user=> (ns foo)
nil
foo=> (defprotocol Foo (foo [this]))
Foo
foo=> (ns bar)
nil
bar=> (defprotocol Bar (foo [this]))
Bar
bar=> (in-ns 'user)
#<Namespace user>
user=> (defrecord Data [x] foo.Foo (foo [this] :foo) bar.Bar (foo [this] :bar))
CompilerException java.lang.ClassFormatError: Duplicate method name&signature in class file user/Data, compiling:(NO_SOURCE_PATH:6)

Or take mutually recursive types. With extend this is no problem at all.
With direct definition you have to introduce manual factories with
declare to break the cycles.

Everyone just reiterates the mantra “It's slower! It's slower! It's
slower!”, but no one talks about the trade-offs. And I bet only a very
small fraction ever checked what “slower” means in their specific use
case.

The problem is not the current implementation. That is perfectly fine.
The problem is a questionable golden idol called “performance” paired
with lack of sorrow research.

Kind regards
Meikel

meb

unread,
May 22, 2012, 2:30:45 AM5/22/12
to clo...@googlegroups.com
Hey Luc,

That's a cool casting strategy to cleanly build a standard way to get at the person piece of any type of applicable record.  In the pure composition case, it's actually a nice solution to build functions know how to unpack the Person aspect of a subtype.

However, I think which strategy is best depends very much on whether you plan to extensively change the protocol implementation between the the different  related types.  In the case where Employee simply carries extra information with regard to a person, I agree that it is nice simply to define a mechanism to extract the base type and just act on it directly.  However if you wish to do something more complicated or override default behavior extensively, I think the encapsulated "newtyped" way is more convenient.

For example, how about we build a little DSL to represent different types of Excel cells.  In Excel, everything is represented as a float, so we have to keep track of the type separately.  I think using a record type is a fairly natural way to encode this extra data on type of Cell, where I might have reached for inheritance in traditional OOP.

(defprotocol Convertable
  (convert-cell [this]))

(defrecord Cell [row col float-value])

(defrecord MoneyCell [cell])

(defrecord DateCell [cell])

(defrecord NumberCell [cell])

(extend-protocol Convertable
    NumberCell
    (convert-cell [this] (float (-> this :cell :float-value))
    DateCell
    (convert-cell [this] (excel-date (-> this :cell :float-value)) ;; Translate an Excel datestamp to java.util.Date
    MoneyCell
    (convert-cell [this] (bigdec (convert-cell (Number (:cell this))))) ;;BigDecimals are for exact money handling.

Blindly unpacking the Cell type (via a Cellable convention like in the Person example) would lose information we need to accurately describe the conversion process and call the best function.  This strategy also leverages ones of the best aspects of protocols: how extensible they are to new types of data.  If I ever decide to support a StringCell type or a BooleanCell, I would just be one extend-protocol away rather than having to rewrite some gnarly embedded cond to work with my new type of cell.  Of course for operations that don't care about their type, creating a protocol to extract the cell and working directly with its attributes (such as manipulating row and position directly), makes a great deal of sense.

Best,
Mark
> clojure+unsubscribe@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
--
Softaddicts<lprefontaine@softaddicts.ca> sent by ibisMail from my ipad!

nicola...@gmail.com

unread,
May 22, 2012, 3:01:31 AM5/22/12
to clo...@googlegroups.com
> Everyone just reiterates the mantra “It's slower! It's slower! It's
> slower!”, but no one talks about the trade-offs. And I bet only a very
> small fraction ever checked what “slower” means in their specific use
> case.

I think the whole thread is about the trade-off: some people complains
that deftype lacks features.
Another problem with extend is that you can't access your instance
variables with
it. And so you end up creating fake Protocols for that, which is even
worse than a big deftype.

Softaddicts

unread,
May 22, 2012, 5:52:06 AM5/22/12
to clo...@googlegroups.com
Bad copy & paste, forgot to remove age from the Personable def.

David Nolen

unread,
May 22, 2012, 6:55:47 AM5/22/12
to clo...@googlegroups.com
Field access works just fine with extend.

Softaddicts

unread,
May 22, 2012, 6:58:01 AM5/22/12
to clo...@googlegroups.com
My strategy here would be different, the conversion fn would be part of
the cell attributes. Maybe with the help of a lightweight factory fn to ease
record allocation.

The Convertable protocol would simply refer to the closure attribute, call it
and pass this as the unique parameter.

The reasons for this choice ?

A) The delta between the derived types is minimal, in your example,
it's not the data that changes, only a variation in the rendering.

B) Rendering is a fundamental operation.
Without it, I think the Cell would be much less useful if not at all.
It's pretty much part of the Cell.

C) Locality.

In real life you would probably have more than one operation that needs a
dispatch. If you create new types and protocols to cope with this, you
will end up with a maze of definitions. Using a helper factory fn to allocate
the record and choosing appropriate dispatchs will
centralize this in one spot.

No heavy cond needed at rendering time, just at dispatch assignment.
The dispatch can then refer to private fns that will take care of rendering
far away from your data type/protocol definitions.

Take your example and add 30 variations of rendering as defrecords and
defprotocol and let me know how your esthetic sensor feels afterward :)

I am not fond of creating variations of defrecords when the data attributes are
the same. Already in Java I cannot stand the russian puppet syndrom where
ancestor classes have a couple of getters/setters less than their descendants :)

Of course if the data properties varies a lot and you end passing nil to many
constructor args, then you have a clear case where it's time to
create different types and corresponding dispatch/protocols. Up to you to
figure when and why...

I try to restrict my use protocols to bridge behavior between different Clojure data types
or to add different behaviors to these, so far so good. My needs for defrecord/deftype
is nearly nil.

I do not try to hide implementation details while operating basically on the same data
representation by adding more definitions.
Other strategies exist that require less code and definitions, dispatch fns is one,
multi methods another....

Luc P.
> > > clojure+u...@googlegroups.com
> > > For more options, visit this group at
> > > http://groups.google.com/group/clojure?hl=en
> > --
> > Softaddicts<lprefo...@softaddicts.ca> sent by ibisMail from my ipad!
> >
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
--
Softaddicts<lprefo...@softaddicts.ca> sent by ibisMail from my ipad!

Meikel Brandmeyer (kotarak)

unread,
May 22, 2012, 7:02:19 AM5/22/12
to clo...@googlegroups.com
Hi,


Am Dienstag, 22. Mai 2012 09:01:31 UTC+2 schrieb Nicolas Oury:

I think the whole thread is about the trade-off: some people complains
that deftype lacks features.

It's not about trade-offs. It's about deftype seemingly lacking features.
 
Another problem with extend is that you can't access your instance
variables with it.

Oh, you can!

user=> (deftype Foo [x])
user.Foo
user=> (defprotocol AFoo (foo [this]))
AFoo
user=> (extend-protocol AFoo Foo (foo [this] (.x this)))
nil
user=> (foo (Foo. 5))
5

The only exception is mutable fields in the type where you don't want uncontrolled access. There you indeed need a protocol to handle the synchronisation of the field access.

Kind regards
Meikel

 

David Nolen

unread,
May 22, 2012, 7:44:32 AM5/22/12
to clo...@googlegroups.com
It sounds like you have issue with working with something as low level as deftype not with protocols nor how they are intended to be used. Most people don't need to bother with equivalence or lookup - they have defrecord.

nicola...@gmail.com

unread,
May 22, 2012, 8:04:13 AM5/22/12
to clo...@googlegroups.com

> The only exception is mutable fields in the type where you don't want uncontrolled access. There you indeed need a protocol to handle the synchronisation of the field access.
>
>

And then you need to include everything accessing the internal state of your data structures in a big deftype with little possibility of reuse.

For example, the earlier hash example cannot be put as an extension.

Another related problem is the fact that lambda do not close over the mutable fields within a deftype. (Or at least that is what i understood from several strange error messages.) FP stops at the doors of deftype...

David Nolen

unread,
May 22, 2012, 8:21:28 AM5/22/12
to clo...@googlegroups.com
On Tue, May 22, 2012 at 8:04 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:
And then you need to include everything accessing the internal state of your data structures in a big deftype with little possibility of reuse.

Access internal state?

For example, the earlier hash example cannot be put as an extension.

Why should most extensions manipulate mutable fields?

David

nicola...@gmail.com

unread,
May 22, 2012, 8:29:35 AM5/22/12
to clo...@googlegroups.com


>
> Access internal state?
>>

Like __hash in your code.

>> For example, the earlier hash example cannot be put as an extension.
>
> Why should most extensions manipulate mutable fields?
>

Most certainly won't. Now none of them can.

David Nolen

unread,
May 22, 2012, 8:42:50 AM5/22/12
to clo...@googlegroups.com
On Tue, May 22, 2012 at 8:29 AM, nicola...@gmail.com <nicola...@gmail.com> wrote:>>
> Why should most extensions manipulate mutable fields?
>
Most certainly won't. Now none of them can.

Good :)

Warren Lynn

unread,
May 22, 2012, 12:06:15 PM5/22/12
to Clojure
Seems to me an approach like this requires too much manual work. If I
know Employee have all the data fields of Person, then I know all
functions working on Person will work on Employee. That is clear and
simple.

In my view, in our programming work, maybe 80% or more time are spent
on simple things and maintenance, so we should not only focus on how
powerful and how clever the language solves difficult problems that
other languages cannot not easily solve. We should also do no worse
than other languages when it comes to those mundane and simple things.
> Softaddicts<lprefonta...@softaddicts.ca> sent by ibisMail from my ipad!

Warren Lynn

unread,
May 22, 2012, 12:10:11 PM5/22/12
to Clojure
Maybe some macros can help. But if it is a good and common pattern,
then it should be included as part of the the language. One thing I am
glad to see in Clojure is it absorbed some known good macros in Common
Lisp (like "awhen" becomes "when-let") so people don't need to re-
invent the wheels again and get things so fragmented (which is
something I am very frustrated with Common Lisp)

On May 21, 7:23 pm, kovas boguta <kovas.bog...@gmail.com> wrote:
> On Mon, May 21, 2012 at 6:08 PM, Mark Engelberg
>
Reply all
Reply to author
Forward
0 new messages