--
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 print-diamond [letter](let [alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZ"position-of (fn [letter] (inc (- (int letter) (int \A))))number-of-letters (position-of letter)dashes (fn [n] (repeat n \-))fixed-text-for (fn [letter] (concat (dashes (dec (position-of letter))) (list letter)))template (map fixed-text-for (take number-of-letters alphabet))pad-with-trailing-dashes (fn [index line] (concat line (dashes (dec (- number-of-letters index)))))top-right-quadrant (map-indexed pad-with-trailing-dashes template)top-left-quadrant (map reverse (map rest (take number-of-letters top-right-quadrant)))top-half (map concat top-left-quadrant top-right-quadrant)diamond (concat top-half (drop 1 (reverse top-half)))](doseq [line (map #(apply str %) diamond)](println line))))I showed it to Extreme Programming and Agile Guru Ron Jeffries, and the following conversation ensued:
I forgot to mention but https://github.com/bbatsov/clojure-style-guide is a pretty good resource.
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/zR5Ny7aoBM0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
--
===========================================
Nice presentation (although I didn't see anything about comments. ?.).
As I mentioned before, there seem to be significantly less examples of capturing semantic knowledge in names. I think a significant reason is the higher level abstractions you get to play with in FP and Clojure.
This leads to a much smaller set of idiomatic solutions to common problems, or to put it another way, I frequently found my journey from OO to FP paved with large chunks of handwritten code which were then reduced with clojure.core functions. The composition of those core functions were then replaced by other existing functions. At the end you frequently realise that what required lots of custom code in Java (for example) could be expressed incredibly succinctly just using the core API.
The succinctness of Clojure compared to Java is also important as those 4 lines of Clojure might well be 20 or so lines of Java.
The other significant fact I think is the lack of ceremony in capturing data models. The vehicle is usually just a map, but I have noticed a significant reduction in the things I want to model. The number of types I have is much smaller in Clojure than java (or groovy or scala etc.). To put it another way, looking back I can see in some of my old projects a non-trivial amount of incidental complexity which just isnt there in the Clojure implementations.
For me, a large part of getting Clojure was realising that it already has its own ubiquitous language and where as in java most problems were unique (or at
least ended up with uniqueish implementations), in Clojure most problems can be expressed in terms of a few powerful abstractions. "Intention revealing" happens because of the sufficiency of the core API/abstractions.
Somebody once wrote something along the lines of "if you end up writing your own Clojure code you are probably doing it wrong". :).
I wouldn't call myself a Clojurian (because of experience, but that not withstanding i don't think I would ever call myself that:)) but it wouldn't surprise me if the claim was made that all of those long labels actually made it less maintainable as they just got in the way of allowing the natural shapes to emerge. Don't get me wrong, in OO land I write code very close to that so I am not challenging the merit, merely suggesting it is solving a problem that isn't so prevelant and isn't the best solution to that problem.
In summary, I at least have found Clojure has a much higher signal to noise ratio, provides a much larger set of design paterns if you will (or has many more idiomatic solutions than Java), and can provide a significant reduction in incidental complexity, thus reducing the problem that some of these approaches are solving.
(It is late in the UK and I hate typing on a tablet horizontally so I hope that made sense! ;)).
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/zR5Ny7aoBM0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
- you're grouping your side-effecting code w/the code that generates the diamond data structure here: https://gist.github.com/ddellacosta/ba7e03951ba1bafd3ec9
While of course the diamond kata is a bit contrived and the point is to print stuff out in the end, it also looks like you are trying to be thoughtful about how you structure your code. So I would suggest isolating your pure functions from your side-effecting code as a sort of basic separation, and avoid monolithic functions like the one I linked to above. This gives you the freedom to apply the data structure to other processes if need be, rather than having to refactor that code later on as soon as you need to do something other than printing to the final diamond data structure. That is a more compositional approach that is good to follow as part of functional programming practice in general. And otherwise it seems like you are following this approach--I think you can see this in the shape of your code overall.
a style like I used in my solution which aims for readable conciseness
Does concise just mean less code?
Concise is short, devoid of noise, and boiled down to its essence to convey the intent effectively. The benefits are far reaching.
Writing code is like throwing ingredients together; making it concise is like turning that into a sauce. It often takes more effort to write concise code. It’s less code to read, but effective code is transparent. A short code listing that’s hard to understand or hides details is terse rather than concise.
Avoid Mental Mapping
Readers shouldn't have to mentally translate your names into other names they already know...
I think you would find it more natural to work line by line vs. the way you constructed blocks and flipped them right/left, and you'd have less code overall.
- Stylistically, I found your naming conventions to be too verbose
there is much less naming-of-concepts. Clojure code tends to be much more about the shape of transformations than the semantics of those transformations.
A case in point, you wrote [code](defn put-one-on-top-of-the-other [top-half-of-diamond bottom-half-of-diamond] (concat top-half-of-diamond bottom-half-of-diamond))[/code]. I think most people would inline that. Extracting it however, give helpful information about the structure which isn't captured by the call to concat, namely the vertical nature (top/bottom). Of course, if the variable names were retained then is also sufficient but they almost certainly wouldn't be.
Here is how Kent Beck describes the pattern in Implementation Patterns:
The first example I saw of this was in Smalltalk. Transliterated [into Java], the method that caught my eye was this:
Explaining Message
The distinction between intention and implementation has always been important in software development. It is what allows you to understand a computation first in essence and later, if necessary, in detail. You can use messages to make this distinction by sending a message named after the problem you are solving which in turn sends a message named after how the problem is to be solved.
highlight(Rectangle area) {
reverse(area);
}
I thought, “Why is this useful? Why not just call reverse() directly instead of calling the intermediate highlight() method?” After some thought, though, I realized that while highlight() didn’t have a computational purpose, it did serve to communicate an intention. Calling code could be written in terms of what problem they were trying to solve, namely highlighting an area of the screen.
Consider introducing an explaining message when you are tempted to comment a single line of code. When I see:
flags|= LOADED_BIT; // Set the loaded bit
I would rather read:
setLoadedFlag();
Even though the implementation of setLoadedFlag() is trivial. The one-line method is there to communicate.
void setLoadedFlag() {
flags|= LOADED_BIT;
}
Sometimes the helper methods invoked by explaining messages become valuable points for further extension. It’s nice to get lucky when you can. However, my main purpose in invoking an explaining message is to communicate my intention more clearly.
And here is a summary of how Beck originally described the pattern in Smalltalk Best Practice Patterns:
ParagraphEditor>>highlight:aRectangle
self reverse:aRectangle
Collection>>isEmpty^self size = 0Number>>reciprocal^1 / selfObject>>=anObject^self == anObject
My program constructs the diamond by putting the top half of the diamond on top of the bottom half. The implementation is simple: the top and bottom parts are sequences that just need to be concatenated. But the implementation is a detail, and may even change one day, so I encapsulate it behind Explaining Message put-one-on-top-of-the-other.
Explaining Message allows us to separate intention (the WHAT) from implementation (the HOW): the method name tells us WHAT, and the method body tell us HOW.
Is the distinction between intention and implementation considered unimportant, or not so important in functional programming?
- Stylistically, I found your naming conventions to be too verbose
if I write a function I only use once, I usually just inline it. Unless of course I believe deep in my heart I'll have need of it somewhere else soon
Given software engineers' infatuation with indirection, it may not surprise you to learn that most refactoring introduces more indirection into a program. Refactoring tends to break big objects into several smaller ones and big methods into several smaller ones.
Indirection is a two-edged sword, however. Every time you break one thing into two pieces, you have more things to manage. It also can make a program harder to read as an object delegates to an object delegating to an object. So you'd like to minimize indirection.
Not so fast, buddy. Indirection can pay for itself. Here are some of the ways.
Here is the refactoring game: Maintaining the current behavior of the system, how can you make your system more valuable, either by increasing its quality or by reducing its cost?
The most common variant of the game is to look at your program. Identify a place where it is missing one or more of the benefits of indirection. Put in that indirection without changing the existing behavior. Now you have a more valuable program because it has more qualities that we will appreciate tomorrow.
I also favor a slightly less verbose style. A function is an abstraction, and you seem to be writing functions for very concrete steps.
You should be much more aggressive about decomposing methods. A heuristic we follow is that whenever we feel the need to comment something, we write a method instead.
Such a method contains the code that was commented, but is named after the intention of the code rather than how it does it. We may do this on a group of lines or on as little as a single line of code. We do this even if the method call is longer than the code it replaces, provided the method name explains the purpose of the code.
The key here is not method length but the semantic distance between what the method does and how it does it.
Hi Leif,
you just need to consolidate the more concrete steps. Something like:
flip-bottom-up -> flip (or vertical- and horizontal-flip)
join-together-side-by-side -> beside
put-one-on-top-of-the-other -> stack (or ontop, or ...)
reverse-every-row -> (map reverse rows) ; very readable to clojure programmers
(let [top-right (create-top-right-quadrant-for letter)
right (stack top-right
(flip top-right))
diamond (beside (map reverse (drop-first-col right)) right)]
(display diamond))
From my own experience I think the following is relevant:
- functions are either specific to a context or abstract. Specific functions need to be understood in the context of their call site and the domain. Trying to make the name of the specific functions capture the entire context leads to noise. I would gently suggest that most of your functions are specific, particularly the ones motivated by extracting intent. They aren't and almost certainly can't be reusable so their call site is part of their context. If you do want the to be reusable then all that context caotured in the label works against you
- again, the drive for reusability and the drive for "future proofing" often lead us astray. In the solution domain there is only one sensible interpretation of flipping vertically
- there is a hint of trying to make the code completely unambiguous and self contained. This is also very similar to trying to make it idiot-proof. I don't think any of these are achievable. (Collective gasp whilst I out on my fireproof coat). The syntax of any programming just isn't expressive enough. Literate programming rocks, but that is because you aren't writing code, you are writing prose
- FP programmers tend to be more familiar with the inbuilt catalogue of idiomatic FP solutions, and dare I say I have met many more "average" OO programmers than FP programmers :). This, coupled with the succinctness of Clojure means idiomatic Clojure already contains a bunch of context. You don't need to tell me what your code is doing because idiomatic Clojure code is inherently readable *once I grok idiomatic Clojure*. For me, I found myself writing that sort of code at the beginning because I was compensating for my lack of familiarity.
Nowadays, I tend to find it much more successful producing code that has a certain number of assumptions:
- it will be maintained for far longer than it took to write (some of our apps are decades old)
- readers will be competant wielders of the toolsets used
- idiomatic code is strongly preferred, as are coding conventions
- reader understands the problem domain and the solution domain
Trying to write code that is somehow a training manual, a design document etc. Is a hiding to nothing. The best communication tool I have found is regular discussions with the relevant people. Corporate mindshare is best maintained through words not code.
I said before, and I think I it needs repeating as you asked again, but no, I dont think FP is any less concerned with the WHY or the HOW etc. I do think it uses seperate tools to achieve the same goals. I would claim that my code still satisfies all of the excellent points rsised in the best practices literature, but Clojure doesn't require the same verbosity.
As ever, this is only my opinion :).
--
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 a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/zR5Ny7aoBM0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
Urk , one bit of my post came across wrong: "I said before, and I think it needs repeating as you asked again" refers to how long these posts are and I thought it had gotten lost in the walls of text. I didn't mean it in the sanctimonious or condescending way it comes across. I really shouldn't try and write these responses whilst entertaining 4 under 10s :).
- Stylistically, I found your naming conventions to be too verbose, with not enough information about the actual input and output--I would prefer a style like I used in my solution which aims for readable conciseness, while documenting what is going in and coming out of my functions. I assume Clojure developers reading my code will have a good understanding of the core data structures and functions available to manipulate them, and so I want to leverage that as much as possible in how I write and document my code.While of course the diamond kata is a bit contrived and the point is to print stuff out in the end, it also looks like you are trying to be thoughtful about how you structure your code. So I would suggest isolating your pure functions from your side-effecting code as a sort of basic separation, and avoid monolithic functions like the one I linked to above. This gives you the freedom to apply the data structure to other processes if need be, rather than having to refactor that code later on as soon as you need to do something other than printing to the final diamond data structure. That is a more compositional approach that is good to follow as part of functional programming practice in general. And otherwise it seems like you are following this approach--I think you can see this in the shape of your code overall.As you said, you weren't looking for alternative algorithms and I recognize that that's not the point. But there are a few things that I think are good and/or common Clojure practice that I think I've internalized, and writing out an alternative solution helped me to see them.Hi Philip,I read your message and immediately wanted to try it myself--I intended to leave it at that but I realized I would be remiss if I did not give you a little bit of feedback based on my experience. I should add that I was kind of fast and loose with my solution (that is, I didn't really read the instructions), but it does print out the diamond shape according to what I saw in the blog post examples.
First of all, here's what I came up with:
https://gist.github.com/ddellacosta/ba7e03951ba1bafd3ec9- I'm assuming you used a TDD process to write this (correct me if wrong--basing that on the articles you linked to), but I think a repl-driven process may be more common for working through a problem like this--i.e. something you can wrap your head around as a whole and solve iteratively. That's not to say I and others don't use TDD in Clojure dev, but just that it's also quite common to do a lot of this kind of development in the repl.- you're grouping your side-effecting code w/the code that generates the diamond data structure here: https://gist.github.com/ddellacosta/ba7e03951ba1bafd3ec9In fact, at this point I prefer using Prismatic's schema (https://github.com/Prismatic/schema) to document as well as provide further safety for my functions, and am of the opinion that Clojure's one glaring weakness is its approach to typing--but that's another discussion and I recognize this is not necessarily a widely-held opinion.More generally, I think reasonable people could disagree on naming conventions and so I would hesitate to say you're doing something "wrong" here--I would rather say: the more Clojure code you read the more you'll get a sense of how people tend to write. You'll figure out what you want to adopt in your own style, and what Clojure devs are going to expect.- I don't want to get too deep into the algorithm itself but I think you would find it more natural to work line by line vs. the way you constructed blocks and flipped them right/left, and you'd have less code overall. I will boldly claim that my solution may be closer to how other developers familiar with Clojure (or functional programming in general) may approach it--not that I'm claiming it's the best approach. I do think it is more concise without sacrificing readability (which is subjective, I fully appreciate).- I don't know if I've ever once used a main function, and you don't see them in libraries, certainly. But that is minor--there's no reason *not* to use it, just that I wouldn't expect to see it.I hope this is useful feedback--good luck in your journey and enjoy Clojure!Dave
2014-12-06 19:48 GMT+09:00 Philip Schwarz <philip.joh...@googlemail.com>:
Hello,--can you please review my first solution to the diamond kata [1] and tear it to bits: let me know all the ways in which YOU would improve the code.I am not so interested in a better algorithm for solving the kata. I am learning Clojure and what I want to know is what YOU would do to make the code more readable/understandable/maintainable, or just to make it follow Clojure idioms and/or conventions that YOU find effective, or to follow a coding style that YOU find more effective.Thanks,Philip
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.
The project.clj file will allow us to manage many different aspects of our application, as well. For example, we could set the foo function from the myapp.core namespace as the entry point for the application using the :main key:
(defproject myapp "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:dependencies [[org.clojure/clojure "1.6.0"]]
;;this will set foo as the main function
:main myapp.core/foo)
The application can now be run from the command line using lein run.
Excellent question and I will be watching this thread with interest.
Similar to David Della Costa, I find a bit difference between Clojure and Java for example is that there is much less naming-of-concepts. Clojure code tends to be much more about the shape of transformations than the semantics of those transformations.
A case in point, you wrote [code](defn put-one-on-top-of-the-other [top-half-of-diamond bottom-half-of-diamond] (concat top-half-of-diamond bottom-half-of-diamond))[/code]. I think most people would inline that. Extracting it however, give helpful information about the structure which isn't captured by the call to concat, namely the vertical nature (top/bottom). Of course, if the variable names were retained then is also sufficient but they almost certainly wouldn't be.
I am on the fence, and fall down frequently either side (you wouldn't believe the chaffing :)) - the more Clojure I write the more comfortable I am with dense calls to core.clj functions. But I also feel the loss of the info captured in variable names/function names as well.
Another point worth mentioning is that the more Clojure you write the more you start to realise that the same "shapes" of functions come up time and time again - the structural shape of the code imparts knowledge sometimes.
As David says, if you haven't looked at Prismatic Schema then have a look. I find the definition of the schema is also an excellent place to capture this extra layer of info in the names of those structures.
Good question.
On Saturday, 6 December 2014 10:48:02 UTC, Philip Schwarz wrote:
Hi, Philip.
I had the same urge as David--I tried it out, glossing over any formal rules. Here's what I came up with:
https://gist.github.com/leifp/ae37c3b6f1b497f13f1e
In truth, I think David's solution is more readable and maintainable. But I think "maintainability" is a pretty tricky concept:
My code makes a seq of maps describing rows, and then turns them into strings at the end. This is probably more work to understand than David's solution. But is it less maintainable? Well, currently, the answer is "yes," but what if I need to output a diamond in several different formats? What if marketing wants each row to be a different color and font? I would start to favor my solution in that case. My point is that the difference between "maintainable" and "horrible" is evident, but the difference between "maintainable" and "easily maintainable" depends on predicting the future somewhat.
I also favor a slightly less verbose style. A function is an abstraction, and you seem to be writing functions for very concrete steps. I think you have most of the correct abstractions for your solution method, you just need to consolidate the more concrete steps. Something like:
flip-bottom-up -> flip (or vertical- and horizontal-flip)
join-together-side-by-side -> beside
put-one-on-top-of-the-other -> stack (or ontop, or ...)
reverse-every-row -> (map reverse rows) ; very readable to clojure programmers
(let [top-right (create-top-right-quadrant-for letter)
right (stack top-right
(flip top-right))
diamond (beside (map reverse (drop-first-col right)) right)]
(display diamond))
The broad takeaway is: if I write a function I only use once, I usually just inline it. Unless of course I believe deep in my heart I'll have need of it somewhere else soon :).
This is somewhat a matter of taste, and again, the requirements history usually determines what gets abstracted into functions, and history can be messy. :)
Hope that helps,
Leif
I forgot to mention but https://github.com/bbatsov/clojure-style-guide is a pretty good resource.
On 9 Dec 2014 00:24, "Philip Schwarz" <philip.joh...@googlemail.com> wrote:
Hello David,I had set myself the constraint that I wanted the solution to exploit two symmetries:(1) The top left and top right of the diamond are mirror images(2) The top half and bottom half of the diamond are also mirror images>I'm assuming you used a TDD process to write this (correct me if wrong--basing that on the articles you linked to)I was on a train commuting back home, and what I did was sit in a loop where I wrote some code and then tweaked it until executing it in the REPL gave me the part of the diamond that I wanted, by eyeballing the console output. What a coincidence that in your gist you linked to http://blog.jayfields.com/2014/01/repl-driven-development.html . I was looking at exactly that blog post on Sunday to determine if what I had been doing could be classified as REPL-based? Still not sure. Thoughts?
(defn print-diamond [letter](let [alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZ"position-of (fn [letter] (inc (- (int letter) (int \A))))number-of-letters (position-of letter)dashes (fn [n] (repeat n \-))fixed-text-for (fn [letter] (concat (dashes (dec (position-of letter))) (list letter)))template (map fixed-text-for (take number-of-letters alphabet))pad-with-trailing-dashes (fn [index line] (concat line (dashes (dec (- number-of-letters index)))))top-right-quadrant (map-indexed pad-with-trailing-dashes template)top-left-quadrant (map reverse (map rest (take number-of-letters top-right-quadrant)))top-half (map concat top-left-quadrant top-right-quadrant)diamond (concat top-half (drop 1 (reverse top-half)))](doseq [line (map #(apply str %) diamond)](println line))))
I showed it to Extreme Programming and Agile Guru Ron Jeffries, and the following conversation ensued:
@philip_schwarz 1st stab at Clojure print-diamond using symmetries identified by @TotherAlistair @RonJeffries @gdinwiddie @sebrose https://gist.github.com/philipschwarz/c7e3be1ac97e482d04bf
@RonJeffries @philip_schwarz can people read that and figure out what it does? i can't but not a closure person. @totheralistair @gdinwiddie @sebrose@philip_schwarz @RonJeffries @TotherAlistair @gdinwiddie @sebrose I like defns of top-half & diamond & think they r graspable-ish; top-left-quadrant less so@philip_schwarz one interesting Q for us all is if one didn't know the prob could one grok the prog @totheralistair @gdinwiddie @sebrose@gdinwiddie .@RonJeffries I think the program is generally easier to grok if you've got the tests, too. @philip_schwarz @TotherAlistair @sebrose
@philip_schwarz Dec 3@gdinwiddie @RonJeffries @TotherAlistair @sebrose agree - I have added tests: https://github.com/philipschwarz/diamond-problem-in-clojure/blob/master/test/diamond_problem_in_clojure/core_test.clj
I notice you did not write tests. I also notice that you added comments to your methods. I like your comments. Find them useful. I am not saying the following applies to your comments, but it will give you an idea of the programming culture I am part of. In that culture, comments are looked at with suspicion:
e.g. 2: "The proper use of comments is to compensate for our failure to express ourself in code." - Robert C. Martine.g. 3: "Comments often are used as a deodorant... often ....comments are there because the code is bad." - Martin Fowlere.g. 4:
- Primary Rule: Comments are for things that cannot be expressed in code.
- Redundancy Rule: Comments which restate code must be deleted.
- Single Truth Rule: If the comment says what the code could say, then the code must change to make the comment redundant.
In that culture, we aim to use certain implementation patterns that make comments unnecessary. Also, where possible, the tests act as (executable, more reliable) documentation.
Moving on, after writing the terse first version of the code, I set out to make my code more readable.
Are you familiar with Robert Martin's dictum?:The Three Functions of a s/w module:* The function it performs while executing* To afford change. A module that is difficult to change is broken and needs fixing, even though it works
* To communicate to its readers. A module that does not communicate is broken and needs fixing.
The rationale for making code more readable is an economic one. Here is a brief summary of Ken't Beck's thoughts on the matter:o Economics is the underlying driver of software designo Software should be designed to reduce its overall costo COST(total) = COST(develop) + COST(maintain)o The cost of maintenance is much higher than the initial cost of developmento Maintenance is expensive because understanding existing code is time-consuming and error-proneo Making changes is generally easy once you know what needs changingo COST(maintain) = COST(understand) + COST(change)+ COST(test)+ COST(deploy)o Learning what the code does is the expensive parto One strategy for reducing overall cost is to invest more in initial development in hope of reducing or eliminating the need for changeo Such efforts have generally failedo Beck’s strategy for reducing overall costs is to ask all programmers to address the cost of understanding code during the maintenance phase, by communicating programmer to programmero The immediate benefits of clear code are fewer defects, easier sharing of code, and smoother developmentTO BE CONTINUED...
Philip
On Saturday, 6 December 2014 13:36:47 UTC, David Della Costa wrote:
- Stylistically, I found your naming conventions to be too verbose, with not enough information about the actual input and output--I would prefer a style like I used in my solution which aims for readable conciseness, while documenting what is going in and coming out of my functions. I assume Clojure developers reading my code will have a good understanding of the core data structures and functions available to manipulate them, and so I want to leverage that as much as possible in how I write and document my code.While of course the diamond kata is a bit contrived and the point is to print stuff out in the end, it also looks like you are trying to be thoughtful about how you structure your code. So I would suggest isolating your pure functions from your side-effecting code as a sort of basic separation, and avoid monolithic functions like the one I linked to above. This gives you the freedom to apply the data structure to other processes if need be, rather than having to refactor that code later on as soon as you need to do something other than printing to the final diamond data structure. That is a more compositional approach that is good to follow as part of functional programming practice in general. And otherwise it seems like you are following this approach--I think you can see this in the shape of your code overall.As you said, you weren't looking for alternative algorithms and I recognize that that's not the point. But there are a few things that I think are good and/or common Clojure practice that I think I've internalized, and writing out an alternative solution helped me to see them.Hi Philip,I read your message and immediately wanted to try it myself--I intended to leave it at that but I realized I would be remiss if I did not give you a little bit of feedback based on my experience. I should add that I was kind of fast and loose with my solution (that is, I didn't really read the instructions), but it does print out the diamond shape according to what I saw in the blog post examples.
First of all, here's what I came up with:
https://gist.github.com/ddellacosta/ba7e03951ba1bafd3ec9- I'm assuming you used a TDD process to write this (correct me if wrong--basing that on the articles you linked to), but I think a repl-driven process may be more common for working through a problem like this--i.e. something you can wrap your head around as a whole and solve iteratively. That's not to say I and others don't use TDD in Clojure dev, but just that it's also quite common to do a lot of this kind of development in the repl.- you're grouping your side-effecting code w/the code that generates the diamond data structure here: https://gist.github.com/ddellacosta/ba7e03951ba1bafd3ec9In fact, at this point I prefer using Prismatic's schema (https://github.com/Prismatic/schema) to document as well as provide further safety for my functions, and am of the opinion that Clojure's one glaring weakness is its approach to typing--but that's another discussion and I recognize this is not necessarily a widely-held opinion.More generally, I think reasonable people could disagree on naming conventions and so I would hesitate to say you're doing something "wrong" here--I would rather say: the more Clojure code you read the more you'll get a sense of how people tend to write. You'll figure out what you want to adopt in your own style, and what Clojure devs are going to expect.- I don't want to get too deep into the algorithm itself but I think you would find it more natural to work line by line vs. the way you constructed blocks and flipped them right/left, and you'd have less code overall. I will boldly claim that my solution may be closer to how other developers familiar with Clojure (or functional programming in general) may approach it--not that I'm claiming it's the best approach. I do think it is more concise without sacrificing readability (which is subjective, I fully appreciate).- I don't know if I've ever once used a main function, and you don't see them in libraries, certainly. But that is minor--there's no reason *not* to use it, just that I wouldn't expect to see it.I hope this is useful feedback--good luck in your journey and enjoy Clojure!Dave
2014-12-06 19:48 GMT+09:00 Philip Schwarz <philip.joh...@googlemail.com>:
Hello,can you please review my first solution to the diamond kata [1] and tear it to bits: let me know all the ways in which YOU would improve the code.I am not so interested in a better algorithm for solving the kata. I am learning Clojure and what I want to know is what YOU would do to make the code more readable/understandable/maintainable, or just to make it follow Clojure idioms and/or conventions that YOU find effective, or to follow a coding style that YOU find more effective.Thanks,Philip
--
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.
--
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 a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/zR5Ny7aoBM0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
You get it when people say, "Oh, I think we need the ability to this kind of thing someday" and thus want all sorts of hooks and special cases to handle things that aren't required. The result often is harder to understand and maintain. If all this machinery were being used, it would be worth it. But if it isn't, it isn't. The machinery just gets in the way, so get rid of it.
A design smells of needless complexity when it contains elements that aren't currently useful. This frequently happens when developers anticipate changes to the requirements and put facilities in the software to deal with those potential changes. At first, this may seem like a good thing to do. After all, preparing for future changes should keep our code flexible and prevent nightmarish changes later.
Unfortunately, the effect is often just the opposite. By preparing for many contingencies, the design becomes littered with constructs that are never used. Some of those preparations may pay off, but many more do not. Meanwhile, the design carries the weight of these unused design elements. This makes the software complex and difficult to understand.
...flexibility is the justification used for the most ineffective coding and design practices. e.g. ... Why all the complexity? Flexibility. Programs should be flexible, but only in ways they change. If ... never changes, all that complexity is cost without benefit.
Since most of the cost of a program will be incurred after it is first deployed, programs should be easy to change. The flexibility I imagine will be needed tomorrow, though, is likely to be not what I need when I change the code. That's why the flexibility of simplicity and extensive tests is more effective than the flexibility offered by speculative design.
Choose patterns that encourage flexibility and bring immediate benefits. For patterns with immediate costs and only deferred benefits, often patience is the best strategy. Put them back in the bag until they are needed. Then you can apply them in precisely the way they are needed.
Flexibility can come at the cost of increased complexity. For instance, ... Simplicity can encourage flexibility. In the above example, if you can find a way to eliminate ... without losing value, you will have a program that is easier to change later.
Enhancing the communicability of software also adds to flexibility. The more people who can quickly read, understand, and modify the code, the more options your organization has for future change.
Hi, Philip.
I had the same urge as David--I tried it out, glossing over any formal rules. Here's what I came up with:
https://gist.github.com/leifp/ae37c3b6f1b497f13f1e
In truth, I think David's solution is more readable and maintainable. But I think "maintainability" is a pretty tricky concept:
My code makes a seq of maps describing rows, and then turns them into strings at the end. This is probably more work to understand than David's solution. But is it less maintainable? Well, currently, the answer is "yes," but what if I need to output a diamond in several different formats? What if marketing wants each row to be a different color and font? I would start to favor my solution in that case. My point is that the difference between "maintainable" and "horrible" is evident, but the difference between "maintainable" and "easily maintainable" depends on predicting the future somewhat.
I also favor a slightly less verbose style. A function is an abstraction, and you seem to be writing functions for very concrete steps. I think you have most of the correct abstractions for your solution method, you just need to consolidate the more concrete steps. Something like:
flip-bottom-up -> flip (or vertical- and horizontal-flip)
join-together-side-by-side -> beside
put-one-on-top-of-the-other -> stack (or ontop, or ...)
reverse-every-row -> (map reverse rows) ; very readable to clojure programmers
(let [top-right (create-top-right-quadrant-for letter)
right (stack top-right
(flip top-right))
diamond (beside (map reverse (drop-first-col right)) right)]
(display diamond))
The broad takeaway is: if I write a function I only use once, I usually just inline it. Unless of course I believe deep in my heart I'll have need of it somewhere else soon :).
This is somewhat a matter of taste, and again, the requirements history usually determines what gets abstracted into functions, and history can be messy. :)
Hope that helps,
Leif
On Saturday, December 6, 2014 5:48:02 AM UTC-5, Philip Schwarz wrote: