(ns app.domain.customer)
(defn make [name, email]
{:name name
:email email})
(defn get-name [customer]
(:name customer))
; Given
(defn make-customer [name email]
{:name name, :email email})
; Examples
(def customer1 (make-customer "Tom" "em...@address.com"))
; vs...
(defn make-name [name] name)
(defn make-email [email] email)
(def customer2
(make-customer (make-name "Tom")
(make-email "em...@address.com")))
--
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
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
(defn make-customer [name email]
{:name name, :email email})
(def customer1 (make-customer "Tom" "em...@address.com"))
But let's say later you decide you want your data model to be {:first-name "Alice", :last-name "Beasley", :email "al...@example.com"}, and you want to change name to be a computed value that concatenates first and last names -- this is going to break all your existing code. If name were only accessed throughout your code via the call `get-name`, then it would be trivial to make this change.
Many OO languages force, or encourage you stylistically, to hide everything behind setters and accessors, so you get for free the ability to change your mind about the data model later.
With Clojure, it is very common to allow your data model to be the API. This puts some additional pressure to get it right the first time. You have to explicitly think ahead about which fields might need to change in the future.
But let's say later you decide you want your data model to be {:first-name "Alice", :last-name "Beasley", :email "al...@example.com"}, and you want to change name to be a computed value that concatenates first and last names -- this is going to break all your existing code. If name were only accessed throughout your code via the call `get-name`, then it would be trivial to make this change.
Sure, that's the theory behind encapsulation, but I'm not convinced there are many cases in practice where the API can remain consistent while the data changes.
Sure, that's the theory behind encapsulation, but I'm not convinced there are many cases in practice where the API can remain consistent while the data changes.I'm not, either. Models that have little or no abstraction --basically aggregates of related items-- end up having APIs that just reflect their contents. For years, my tools were the Eclipse refactoring features and the code (re)generation of the Eclipse Modeling Framework, not encapsulation. I started using Clojure practices, though not totally convinced until a while ago Stuart Sierra wrote: "The data *is* the API," which helped me understand what I was really doing. Whereas I never faced the proverbial computed-field change, I've now taken good advantage of generic functions over maps and records.
--
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
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
I am new to clojure.My question how to handle version if we expose data directly as api, any schema change will break the api which may impact third party users.
On Friday, 17 October 2014 15:23:10 UTC+11, Armando Blancas wrote:Sure, that's the theory behind encapsulation, but I'm not convinced there are many cases in practice where the API can remain consistent while the data changes.I'm not, either. Models that have little or no abstraction --basically aggregates of related items-- end up having APIs that just reflect their contents. For years, my tools were the Eclipse refactoring features and the code (re)generation of the Eclipse Modeling Framework, not encapsulation. I started using Clojure practices, though not totally convinced until a while ago Stuart Sierra wrote: "The data *is* the API," which helped me understand what I was really doing. Whereas I never faced the proverbial computed-field change, I've now taken good advantage of generic functions over maps and records.
--
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
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
I am new to clojure.My question how to handle version if we expose data directly as api, any schema change will break the api which may impact third party users.
Actually, I think that this is a real problem with Clojure, and with
data access. It is very hard to change between accessing a var as a
value and through calling a value.
http://en.wikipedia.org/wiki/Uniform_access_principle
To my knowledge, Clojure cannot do this.
When I first wrote the var, I thought it was going to be a constant.
However, during the development of my code base, I discovered in one or
two small uses cases, it could not be.
> With regard to automatically :doc string, why can't you set it when the var
> is created?
Two reasons. The first is specific to my library, which is that the var
contains a mutable Java object. So the :doc string may change over time,
independently of the value of the var. "Don't use mutable objects" would
be an obvious response, but not a useful one in this case.
James Reeves <ja...@booleanknot.com> writes:
>>
>> Actually, I think that this is a real problem with Clojure, and with
>> data access. It is very hard to change between accessing a var as a
>> value and through calling a value.
>>
>
> Curiously, this is something I don't think I've ever run into.
>
> Perhaps I'm the outlier, but both the examples you give seem a little odd
> to me.
I don't know who is the outlier. The point is that Scala, for instance,
has explicit support to hide the distinction between accessing a value
and computing a value. The point is to support the uniform access
principle.
http://en.wikipedia.org/wiki/Uniform_access_principle
To my knowledge, Clojure cannot do this.
In Clojure, non-computed fields are usually accessed directly by keyword, whereas computed fields require an actual API. This difference in access style complicates things if you want to change which things are stored versus computed.
--
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/v03o5MWys9E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
Yeah, it's hard to deny the convenience of Clojure's keyword lookups and standard assoc mechanism for getting and setting stored values, but I think Bertrand Meyer's Uniform Access Principle reflects some pretty deep thinking about the kinds of complications that arise in maintaining large programs. Although the Clojure community mostly rejects the Uniform Access Principle right now, as people start writing larger programs in Clojure, and need to maintain them for longer periods of time, it will be interesting to see if the pendulum swings back in favor of uniform access.
--
--
I think all of James' points about the proven value of structuring an application primarily around data rather than a complex API are right on point. It is one of the things I love about the Clojure philosophy.But there's nothing about the value of data-driven development that requires data lookups and data computations to be so different. There's plenty of room for Clojure to have continued evolution in this area and still preserve the essence of its approach.
I don't know who is the outlier. The point is that Scala, for instance,
has explicit support to hide the distinction between accessing a value
and computing a value. The point is to support the uniform access
principle.
http://en.wikipedia.org/wiki/Uniform_access_principle
--
Interesting. So, if you resolve http://www.clojure.org, is this data or
is it computed?
James Reeves <ja...@booleanknot.com> writes:
> Yes, Clojure pretty much rejects the idea of uniform access.
I don't think it does. I think it just does not support it which is a
somewhat different thing.
Still, although my example might appear irrelevant to Clojure in
general, I don't think it is. Clojure's current docstring support is not
very good (and there was a thread here about it not long ago). It would
be nice to have something richer. For really rich documentation,
maintaining the docstrings is a different file might make sense. Clojure
can support this at the moment, but only in the way that I have -- the
var metadata could be updated at load time. What it cannot do (easily)
is support lazy loading of the file documentation at the point of first
use, because the decision was made originally that :doc metadata is a
value and is NOT computed.
> The largest systems we've developed, including the web itself, are data
> driven.
Interesting. So, if you resolve http://www.clojure.org, is this data or
is it computed?
I don't think you can tell. The web supports the Uniform Access
Principle.
The uniform access principle is about having uniform access to data and
APIs. It's not about prefering one or the other.
Yes, which is what I have done, of course. Now it won't work in any IDE
which looks for the docstring as :doc metadata. It is totally
unextensible. I do not think that this is good.
> The response may be computed, but once it's sent to the client it's
> immutable data.
Well, that's the point, you cannot tell. So, for example, I can provide
a website implemented over a relational database. Or I can serialize it
out as static files. Or I can add a caching layer which serializes
lazily, and redoes every hour.
> The response returned has no inherent API associated with it.
That sort of depends on the response.
> To put it another way, consider the function:
>
> (defn build-user [first-name last-name]
> {:first-name first-name
> :last-name last-name
> :full-name (str first-name " " last-name)})
>
> Like a website, a function may perform computations, but its return
> value is immutable data. Would you say the above conforms the the
> Uniform Access Principle?
As I said at the beginning, it is possible to achieve UAP by making
*everything* a function. So, we can automatically achieve UAP by ONLY
using functions and never values.
Well, the question is, where does this additional complexity come from.
In Java, it results in enormous quantities of boilerplate get/set
methods. In Scala, these are autocoded away.
James Reeves <ja...@booleanknot.com> writes:
> Clojure prefers "simple" solutions over "easy" solutions.
A nice aphorism sometimes, but content free in this case, I think.
So, there is this tourist, and he asks a local, "how do I get into
town". "Hmmm," says the local, "if I want to get into town, I wouldn't
start from here".
So there's this business owner, and he asks a local, "What's the quickest route for my vehicles to get to town?" The local considers, and replies, "You'd be able to get into town much faster if your vehicles were stationed on the other side of town."
Which is nice and simple, yes. And has negative consequences in terms of
extensibility. I understand if you are happy with this compromise. But
it is a compromise.
Well, the question is, where does this additional complexity come from.
In Java, it results in enormous quantities of boilerplate get/set
methods. In Scala, these are autocoded away.
I don't know who is the outlier. The point is that Scala, for instance, has explicit support to hide the distinction between accessing a value and computing a value. The point is to support the uniform access principle. http://en.wikipedia.org/wiki/Uniform_access_principle To my knowledge, Clojure cannot do this.
But there's nothing about the value of data-driven development that requires data lookups and data computations to be so different.
The good news is that if the community does start to see more value in uniform access, achieving that is just a few macros away.
(defn add-customer [customer]
; ...
)
(defn send-message [customer message]
(send-email (:email customer) message)) ; no error checking because called from internal functions only
(defn add-customer [customer]
(when (customer-is-valid customer) ; error checking because likely to be called externally
(add-to-database customer)
(send-message customer "Welcome")))
Hello Clojure people,First up, apologies because this is going to be a long message. Howver, if you do have the time to read and respond, then I would be extremely grateful!Recently I've decided to give Clojure a proper go. Over the past year or so I've paid it a bit of attention: I've read books all about how to use it and I've spent a bit of time working through tutorials to create little web apps and so on. I understand the language (although not to any great depth yet), but I'm still unsure about the best approaches to actually working with it.I've got many years of OOP experience, and I'm a big fan of principles like SOLID and approaches like DDD. What I want to do now is, try and learn how to build well designed models using Clojure, while using best practices! I've started having a bit of a crack at it by building a simple app, but I'm finding myself trying to transfer a lot of my existing OOP techniques into Clojure. It's working, but I'm wandering if I'm overdoing it or perhaps just not doing things "the Clojure way".So, my very first question is are there any good books or resources on modelling, application architecture or best practices, using Clojure?Next up, maps vs records. I've read that the typical approach is to use maps until the design starts to solidify and then you can move to records. Is the the general attitude of Clojure developers or do some like to dive straight in with records?The next question is encapsulation: I've taken the approach that I'm kind of using namespaces like classes. If I want to create customer entity I create a namespace for it, then add a "make" function to it which returns a map of the right "shape". I'm then shying away from accessing the map directly, and rather doing it through other methods in the namespace which take the instance as a parameter. E.g.
(ns app.domain.customer)
(defn make [name, email]
{:name name
:email email})
(defn get-name [customer]
(:name customer))Is this a reasonable approach? If not, what might be a better one?This leads on to safety and defensive programming. Using the approach above, how much "type safety" is required? Obviously, in any API which is going to be used by other programmers, you've got to handle bad inputs well. But what is the Clojure approach in the "domain model"? Coming from the DDD mindset, where models are designed to be un-breakable, part of me wants to use records for all entities and typehint for them everywhere. However, I wander if the Clojure way is more about rigorous testing to make sure the wrong values don't get put in in the first place? Also, what about libraries like https://github.com/Prismatic/schema, this could be used in :pre conditions to be more explicit, is that a common thing?Next up, how far do you go with creating "types" vs using primitives? Again, referring back to DDD, both the email and the name in the example above are suitable candidates for value objects. In Clojure, would you consider hiding the data behind a set of specialised functions to create, access and use it? Or would you just pass the primitive string/map/vector/whatever about and work on it directly? Example:
; Given
(defn make-customer [name email]
{:name name, :email email})
; Examples
(def customer1 (make-customer "Tom" "em...@address.com"))
; vs...
(defn make-name [name] name)
(defn make-email [email] email)
(def customer2
(make-customer (make-name "Tom")
(make-email "em...@address.com")))
I think that's all I want to ask about now. I do have other questions about dependency inversions, but I'll leave that for another time. If you've read this far then thank you very very much! Also,I know that no one really wants to just sit and read though random peoples code, but, if you are interested in my (very very early stages) experimental project, then I've put in on https://github.com/tomphp/clojure-cocktails - any comments, critiques, PRs or questions would be really great!Thanks you so much for your times and I look forward to any thoughts or suggestions!Tom
Okay. I can give you a very concrete example, and one where I think that
it probably has been actually useful to you.
Imagine you write the following piece of code:
(first l)
This returns a value. If we obeyed the universal access principle,
however, we would not know whether this resulted from computational or
otherwise. And, in fact, Clojure does exactly this.
The distinction of Clojure is not that it prefers data over APIs, but
that it prefers relatively few APIs, reused where possible.
--
Anyway, I found this talk very helpful when I started learning Clojure, getting used to data-oriented thinking:
James Reeves <ja...@booleanknot.com> writes:
> So you're saying laziness and UAP are the same thing in your view?
I am saying that UAP enables you to implement laziness freely.
Regardless, we have a nice example in Clojure, where we not
distinguishing between data and computation allows us to do something
nice.
All of this discussion has made me think I should revist the issue. I
could put my own Map implementation on my vars as metadata, and have
this map work lazily, so that the calculation of the :doc metadata
happens through computation.
That would be evil.
--
the summary is good, but I'm missing the more efficient data structure array-map that probably wastes less space than the hash-map for the same size of object.
James Reeves <ja...@booleanknot.com> writes:
>
>> Regardless, we have a nice example in Clojure, where we not
>> distinguishing between data and computation allows us to do something
>> nice.
>
> Yes... I agree it allows us to do something, but let's agree to disagree on
> whether that something is "nice" :)
Laziness? In my experience it is not nearly as useful as it is suggested
to be, but it does seem to be pushed as a feature of clojure.
On 22 October 2014 10:01, Phillip Lord <philli...@newcastle.ac.uk> wrote:James Reeves <ja...@booleanknot.com> writes:
>
>> Regardless, we have a nice example in Clojure, where we not
>> distinguishing between data and computation allows us to do something
>> nice.
>
> Yes... I agree it allows us to do something, but let's agree to disagree on
> whether that something is "nice" :)
Laziness? In my experience it is not nearly as useful as it is suggested
to be, but it does seem to be pushed as a feature of clojure.No, I mean unrestricted uniform access.Clojure's laziness is restricted to seqs
and is guaranteed to always produce the same value for the same field.
That said, seqs still have their issues, even with their inherent restrictions. For instance, it's easy to accidentally hold onto the head of a seq, particularly if it's passed in as an argument to a function. For example:(defn print-seq [xs](doseq [x xs]
(println x)))
On Wednesday, October 22, 2014 6:09:04 AM UTC-4, James Reeves wrote:No, I mean unrestricted uniform access.Clojure's laziness is restricted to seqs
Not quite; there's also delay and force, and it's possible to use these to construct other lazy data-structures.
and is guaranteed to always produce the same value for the same field.
Nope:
=> (def foo (int-array [1 2 2 5 9 3]))
#'user/foo
=> (def bar (seq foo))
#'user/bar
=> bar
(1 2 2 5 9 3)
=> (aset foo 3 3)
3
=> bar
(1 2 2 3 9 3)
That said, seqs still have their issues, even with their inherent restrictions. For instance, it's easy to accidentally hold onto the head of a seq, particularly if it's passed in as an argument to a function. For example:(defn print-seq [xs](doseq [x xs]
(println x)))
Locals clearing should prevent head-holding in this case, since xs isn't referenced after the doseq, shouldn't it?
Clojure's laziness is restricted to seqs
and is guaranteed to always produce the same value for the same field.
Nope:
=> (def foo (int-array [1 2 2 5 9 3]))
#'user/foo
=> (def bar (seq foo))
#'user/bar
=> bar
(1 2 2 5 9 3)
=> (aset foo 3 3)
3
=> bar
(1 2 2 3 9 3)
user> (def foo (int-array [1 2 2 5 9 3]))#'user/foouser> (def baz (map identity foo))#'user/bazuser> baz
(1 2 2 5 9 3)
user> (aset foo 3 3)3user> baz
(1 2 2 5 9 3)
and is guaranteed to always produce the same value for the same field.
Nope:
=> (def foo (int-array [1 2 2 5 9 3]))
#'user/foo
=> (def bar (seq foo))
#'user/bar
=> bar
(1 2 2 5 9 3)
=> (aset foo 3 3)
3
=> bar
(1 2 2 3 9 3)
Yes, seqs are presented as persistent collections, so this situation
is quite unfortunate. But in the case of seq over mutable array, I
think practical/performance reasons win over the correctness, which
would require caching of realized items.
Jozef
On Wed, Oct 22, 2014 at 7:51 PM, Ambrose Bonnaire-Sergeant
<abonnair...@gmail.com> wrote:
> Sorry, to be specific I'm disturbed by the apparent mutation of an immutable
> data structure after it has been observed!
>
> Thanks,
> Ambrose
I missed this in the sequence spec: When seq is used on native Java arrays, changes to the underlying array will be reflected in the seq - you must copy the source array to get full immutability.I understand the rationale, still quite shocking.AmbroseOn Wed, Oct 22, 2014 at 3:26 PM, Jozef Wagner <jozef....@gmail.com> wrote:Yes, seqs are presented as persistent collections, so this situation
is quite unfortunate. But in the case of seq over mutable array, I
think practical/performance reasons win over the correctness, which
would require caching of realized items.
--
To their a wrench in the works, "keywords are functions that look themselves up in a map". Therefore, a traditional map is just as much api as data
In Clojure, would you consider hiding the data behind a set of specialised functions to create, access and use it? Or would you just pass the primitive string/map/vector/whatever about and work on it directly?
;; Bad
(defn complex-process []
(let [a (get-component @global-state)
b (subprocess-one a)
c (subprocess-two a b)
d (subprocess-three a b c)]
(reset! global-state d)))
;; Good
(defn complex-process [state]
(-> state
subprocess-one
subprocess-two
subprocess-three))