6 ms·
I've never really needed it in Clojure. The code is that much more expressive that overall LoC is dramatically reduced (thus more easily viewed "in the whole"),
by jarcane 11y ago
I've never really needed it in Clojure. The code is that much more expressive that overall LoC is dramatically reduced (thus more easily viewed "in the whole"), and following a proper functional approach means good functions should be largely interchangeable and not dependent on any global state. So on the rare occasion I need to move something around, it's just meant a quick copy and paste and tweaking a few references.
Yes, Clojure is dynamically typed, but I think you're missing the gains you get from FP and expressiveness in terms of reducing labor.
- eurekin 11y agoI was trying to find time for Clojure, but I had always had doubts - exactly as the grandparent commenter... He touched upon what I think is essential. The biggest things to lose by giving up Java is the tooling. Using the IDE I have many possibilities to learn about code, without ever needing to run it. For example by using find usages/implementations. The types on methods often give out hints about the big picture. Like, if a method passes a JAXB context, than I know I'm in the infrastructure layer. If I'm looking for a business rule "how is the price calculated", I can very quickly cut off a large portion of code which does not do it. After few iterations, it's almost certain I will get to the relevant code part. Then I typically launch an application and put a breakpoint, to test my assumptions. After that, I have a lot of confidence about how the application behaves in that spot, even If I saw it for the first time. If anybody asks me "Tell me if we can extend the functionality of calculating user score to include expert rating", I can provide a good effort approximation, even in a never seen before application. I just know, that given the tooling, I can filter out unrelated code parts and concentrate only on the relevant one. The above concerns only code comprehension of a large project. The tooling does give you also power to do macro level restructuring, like breaking a large monolith into smaller modules. The compile time errors are like a high accuracy automatically generated todo-list. Since it takes so little effort to make large changes, you gain the ability to do it multiple times and refine. If I broke something into two modules and a dependency cycle came up, I see it right away and can do everything again, a little bit better with the extra knowledge I got. I have never seen anybody doing the same in Ruby, maybe because I've been working only about two years commercially in it, but a lot of times I've seen something quite opposite: introducing more hacks decreasing code quality, just to work around some conceptual error. While working in Java, I have seen a lot of situations like "we'll doing a hack here and the real solution will come with the next big release" and really the solution does come. High level changes typically break a lot. With tooling, you gain confidence you can fix them, without introducing a lot of regression bugs. In Ruby, I saw the people just give up and leave monkey patches around. Then they just leave the project for the next unfortunate programmer. So, back to the topic. You say that the change name refactoring is very simple in Clojure, because the impact of change is local to the method. My question is: How does Clojure tooling aid You in changing a place with a global impact? Like, changing the argument list of a very often called method (e.g. the method to serialize a structure to string to require the caller to provide an explicit encoding)? Can You, right after doing the change, quickly estimate how much work is there to fix all affected places? Do You have confidence that the list you provide is complete and doesn't miss anything critical? How certain can you be without running the code, only analyzing?
- jarcane 11y ago1) That's up to the tests, just as in any language. 2) Some of the changes you describe are no less trivial to experiment with in an environment with a REPL. I routinely trial changes to small functions simply by running them in the REPL as discrete units before attempting to re-start an entire server project. 3) Good FP strategy rarely means dealing with anything with a global impact. About the only exception is client-side ClojureScript, where keeping a single "state" atom is largely idiomatic, and even then, actually using it is a last resort action reserved for things like routing and user session, preferring instead local state unique only to a single file for anything else that requires it. I think this is a case where you need to experience just how big the difference is between an expressive FP approach and something like Java is to really understand why it's not a problem, and indeed, amounts to something like a category error. When a Java programmer asks me about refactoring Clojure features, I can't help but think about stuff like how the entire messaging system for the app I worked on over the summer fits on maybe 3 or 4 printed pages of code and only took a week and a half to complete, compared to say, EnterpriseGradeFizzBuzz ... The tools you describe sometimes don't exist, it's true, but that's because no one needs them. They exist to automate tedium and complexity that is largely idiomatic to Java and other similar OOP languages, but not expected, desired, or necessary in other paradigms of programming. I know it's hard to believe, but there are other ways of doing things. We do alright, I promise.
- eurekin 11y agoThank You for the answer! After I reread my own comment I realized it sounded much more harsh than I wanted. Good you were not put off by the tone. Sorry for that. I have to admit that my biggest pet peeve with working in Java projects are those high impact changes. I try as much as I can to propose "extending" changes, but working in a team means someone certainly at some point will propose "changing" how something works, without ever realizing all the consequences. If by using Clojure you get the same benefits of what the best OO practices can give, than I'm willing to give it a try. I do remember a presentation about working on immutable snapshots of reality, it was something along the lines "How do you normally check if a runner had both feet above the ground?". The example there was, that a typical imperative programmer just checks the first foot and then the other. Naturally, the second check happens some time after the first one. The selling point was that, in Clojure, that you operate on a immutable snapshot of a runner in time, so the feet are by default in the same point in time. So, I'm liberated from thinking about a whole category of potential errors. I liked it a lot. There are some other categories of potential errors as well. I mentioned them in my comment and you cleared that up for me. Still I have another doubt, regarding a typical code reuse scenario. A simple example follows. Let's say a programmer needs to ask the user for input. He writes a code, which pops up a user interface dialog and wires up an action to the buttons callback. All is fine. As the time passes, there is a need to provide more questions to the user. The dialog code grows. Someone files a bug, that, in a multi-monitor setup, the dialog shows up always on the wrong screen. The bugs get fixed. More dialogs are introduced. Another project starts up and wants to use the nice UI code. We designate it for a separate library. The first project must isolate and make everything generic. The requirement is that both projects must work on top of that library and the first one cannot loose any functionality. This is where the tooling excels most. You just make the duplicated UI code look the same and use the "extract method" refactoring. The IDE tells you "I just found 5 duplicates, should I replace them all?". You answer yes. The IDE even tells you if there are any potential side effects. If there are none, you proceed. Then you use "use interface instead of method call" refactoring. In that way you can nicely extract contract between the client code from the library code. You move the stuff around, so that the library code lands in other project. Then, You can just wire the library as a dependency in the Project B. It happens, that some needed functionality is not exposed. So you change the library and immediately see the impact on the A Project. The case here is: you do not have to understand the A Project as a whole, since the tooling takes care of so much of the static dependencies. You just pull out the fun stuff and patch out the wounds. It does not require a lot of skill or knowledge, so a new project member can also be designated with a such task - not only the knowledgeable rock-star programmers that have the whole project in their head. If you can assure me that in Clojure you can do the same in a safe manner than I'm all in. :) I'd appreciate any articles or stories explaining such scenarios in detail.