14 ms·
I'd like to learn a modern production-ready functional language (I have some academic experience with SML), since they seem like a good way to grow as a program
by m12k 8y ago
I'd like to learn a modern production-ready functional language (I have some academic experience with SML), since they seem like a good way to grow as a programmer. Main contenders so far are Haskell, Scala and Clojure (Reason might go on the list soon too) - but the fact that Clojure is dynamically typed is a bit of a turn off for me. My experience with dynamic vs static typing is that as a system grows in size and complexity, the value of static typing goes up. Doing a major refactor is one of those things that I find especially error-prone in dynamic languages, while statically typed languages make this much less daunting (of course they come with the opposite issue that you might not be able to compile for a day until you've fixed all the type errors - but I'd take this trade-off any day of the week). Can anyone with experience comment whether Clojure suffers from this too or if it somehow handles the lack of static typing better than other dynamic languages?
- gregwebs 8y agoYou can't refactor Clojure without fear like in Haskell. It is actually possible in Haskell now to defer type errors to runtime, but I haven't heard of a practice of using that, even to aid in refactoring. See this comment: https://news.ycombinator.com/item?id=18345672 https://news.ycombinator.com/item?id=18345672
- akmiller 8y agoI'd disagree with this. Primarily been developing with Clojure for 5 years now with some pretty large codebases. It does depend on how you write your code but favoring pure functions, pushing immutability to the edges of your programs allows you to refactor without fear. Clojure also has many things to aid in this such as pre/post conditions, clojure.spec (which allows you to build complex type definitions), and of course test.check (property based testing).
- lgas 8y agoHaving spent the last two years working professionally in Haskell after having spent the previous two years working professionally in Clojure, I'd disagree with this. Haskell is roughly infinitely better than Clojure for meaningful refactoring. Clojure gives you (great) tools for using your own brain to make sure the refactor goes well. Haskell replaces your brain almost entirely in the process and just gives you a precise list of the things have to fix. The difference in experience is night and day. When you're done with a Haskell refactoring (even a major one) and it compiles again you will be completely done 99% of the time. With Clojure there's not even a "compiles again" moment to look for, and even when you think you're done you're refactoring there's a good chance that there's something that will pop up at runtime (and some point arbitrarily far in the future) and tell you otherwise.
- akmiller 8y agoTo be fair, I wasn't comparing the two. I've never used Haskell (for more than just learning/tutorials). I would suggest that if you have runtime bugs popping up in Clojure programs then that would suggest the inputs to functions (since they should be primarily pure) are not being validated which can easily be accomplished. I would imagine this needs to be done in Haskell as well since just verifying types does not indicate valid data.
- matt-noonan 8y agoIn Haskell designs, developers tend to be quite careful about setting things up so that verifying types does indicate valid data. Coming from other languages (C++, Common Lisp, and Python in my case), it can be a little surprising just how often and how easily you can make this happen.
- akmiller 8y agoYou can do similar things with spec for Clojure. As I stated in another response that is opt-in obviously so it requires more discipline perhaps but it is definitely available.
- willtim 8y agoNull checks are also opt-in, that's why Tony Hoare calls null his "Billion Dollar Mistake". Explicit runtime checks are in no way "similar" to static typing! Static typing is a machine-checked proof of correctness for a certain class of properties.
- matt-noonan 8y agoI'd rather have the compiler ensure that my discipline (and my team's) never slips, and to get guarantees about all possible executions instead of just those executions that have been tested. For these reasons and others, I don't personally find run-time checks to be an adequate replacement for compile-time checks. But there is no really convincing research on the subject, and I certainly don't begrudge your preference here.
- tanin 8y ago> It does depend on how you write your code but favoring pure functions, pushing immutability to the edges of your programs allows you to refactor without fear. Dynamic typing is punitive if we make mistake (e.g. using the wrong architecture, now we have to refactor), and we'll make mistakes. Writing perfect code with perfect architecture and perfect test has been the argument "for" dynamic typing, but I don't think that's a realistic assumption.
- yogthos 8y agoMy experience is that static typing tends to lead to higher coupling and monolithic design, while you tend to break things up more aggressively in a dynamic language like Clojure.
- tanin 8y agoI understand what you mean, and it's definitely a trade off. Static typing requires all types to make sense together no matter how small the addition is. In dynamic typing, we can just add a class/object/field here and there because it'll only be used narrowly anyway. So, it's often fine. This would be great if we can think of a small example to illustrate this trade off.
- yogthos 8y agoOne example I can think of is how Ring middleware works. The request map is passed through a series of functions, and each one can modify it in its own way. For example, you could have an authentication library that adds an :identity key, and another that adds a :session key. These libraries can work together without having to know anything about each other.
- darksaints 8y agoClojure isn't nearly as bad as most would have you believe. I still prefer static strong typing, but since Clojure is strongly typed usually you'll find most of your errors within a few rounds of execution. And they have an excellent implementation gradual typing if that's your thing. If you value dynamic types, this is about as good as you can get.
- ken 8y agoFor some reason I find it funny that all the comments here about static typing are written in English, a language lacking not only static typing, but a syntax that's possible to automatically check. It lets one say things like "I still static strong typing" (which you've since corrected)! One could write a comment here in Haskell describing why static typing is better, yet nobody ever does. I think even the most ardent static typing proponents are implicitly admitting that dynamic typing is fine, and other concerns can be more important, in some contexts.
- Jach 8y agoYou would probably enjoy this talk by Guy Steele where he discusses the language of mathematics that computer science people use in their papers (Computer Science Metanotation) to talk about other languages. Funnily it's untyped and lacking a rigorous specification... https://www.youtube.com/watch?v=dCuZkaaou0Q https://www.youtube.com/watch?v=dCuZkaaou0Q
- jwhitlark 8y agoThere's a reason why we teach math in kindergarten and proofs in college. There's lots of things that are useful to do without the rigors of rigorous proof.
- bunderbunder 8y agoYou can't necessarily do it in Haskell, either, on account of the whole partial functions thing.
- preordained 8y agoOr lack of great IDE...the best type system seems a waste without a right-click refactor option to show for it. I'm not going to chase compilation errors file by file...
- jose_zap 8y agoIt has a language server which works with many good editors like vscode, that’s something at least!
- KingMob 8y agoI use Clojure in the large, and the lack of static typing isn't something I miss. FWIW, spec allows you make similar guarantees if you use it (technically more, since you can express more than HM type systems). I think the static/dynamic divide speaks to deep divisions in programmer personalities, but if you're open to suggestion, I would put it like this: As Haskell/OCaML are to static langs like Java, Clojure is to dynamic langs like Python/Javascript. Clojure has a deep vein of pragmatic simplicity throughout its core libraries and community that I think does more for taming large-scale development than static typing does, but that just my $.02.
- jwhitlark 8y agoIn my experience, static typing was somewhat helpful in monolithic projects, but when I saw it used in heterogeneous distributed systems, it seemed to lose much of its benefit and impose new costs. I'm curious if we're going to see a big new synergy between Kubernetes (represent everything as data) and Clojure (great with data).
- tanin 8y agoIs Spec simply a test? Or is it something more? I keep hearing about it, but it seems to be more than just a test case. Let's take a simple example. I have an object with the method named "get". But I call "fetch" in my code. When will I see this error? During compile time or run time?
- fazzone 8y agoIn Clojure unresolved names are a compile failure, and Spec would not be involved. Spec is just a nice library for writing runtime assertions about data. An example would be calling a third-party API and then validating the response with Spec to make sure it matches your expectations, or even validating the responses you are about to return from your own API. Similar to a type system the Spec can also serve as a sort of documentation about what's in the data structure.
- ahjones 8y agoThis is spec: https://clojure.org/about/spec https://clojure.org/about/spec All spec errors turn up at run time. See the guide for more details: https://clojure.org/guides/spec https://clojure.org/guides/spec. You can combine spec with test.check (Clojure's quick check library) https://clojure.org/guides/spec#_generators https://clojure.org/guides/spec#_generators
- saosebastiao 8y agoI've used all three, and at least in my opinion, Scala is the most production ready by miles. Clojure is great if you like dynamic typing...by far the best dynamically typed functional language out there. Beware of claims that clojurescript and clojure are the same language. While both are dynamically typed, clojure's type discipline is strongly enforced (type errors will throw exceptions) while clojurescript will fail silently. I personally know of a team that experienced a million dollar bug because of this subtle distinction...you absolutely have to be prepared for it if you plan on using both. If you like gradual typing, there are some great options. Haskell is good but the language's strictness (especially around effects) is very demanding. This can be both a good thing and a bad thing. In my experience/opinion, it's a good thing in some use cases and a bad thing in other use cases...but there's no way to opt out of it when it's a bad thing. Further, the ecosystem is far less useful than the jvm ecosystem. Scala definitely has its faults, but its type system is extremely expressive without being extremely restrictive. I've never felt restricted by it (like I did with Haskell), but it regularly blows me away how easy it is to refactor and fix errors. The Java interop is far more intuitive than Clojure. Drawbacks: The only really useable IDE is Intellij. Some libraries tend to form an ecosystem of dependencies which don't play well with other ecosystems (ie some libraries are "Scalaz-only", some are "Cats-only", etc), which can be really annoying at times. Java interop isn't extremely straightforward in the case of collections (if that is a big concern, go with Kotlin which is phenomenal).
- didibus 8y agoClojureScript is strongly typed. I'm curious to know the specific case they ran into? I suspect it might have been interop with JavaScript, because you shouldn't have weak types in ClojureScript.
- darksaints 8y agoNo, it's not. Try `(+ 1 "1")` in clojure and clojurescript. In clojure, it produces a runtime exception. In clojurescript it silently errs, resulting in a "11". The compiler correctly infers and warns of the problem, but that is of no use if your inputs change types on you. In the case of the service I'm referring to, an external service started serving up json strings for large numbers, whereas it had previously been json numbers. Since there was no exception produced, it silently corrupted data for a couple weeks before it was caught.
- blandflakes 8y agoThere are a lot of people who claim they're more productive in Clojure, in spite of or because of its dynamic typing. I'll just throw my anecdatum out here: I found that dynamic typing was a major pain even on my own personal projects. I also never found REPL-driven development to mesh well with my workflow. In Scala, for example, entire classes of error that I just shouldn't be able to make don't exist. Maybe I'm just the kind of person who works better with static typing. Clojure overall is an excellent language with really good features. It just won't get out of my way sometimes.
- xapata 8y agoTry to grok REPL-driven development before dynamic typing. You won't like the latter until you love the former.
- jerf 8y agoHaskell does REPL-driven development fairly well. It's not the best REPL, but it works, especially if you take a moment to learn a few of the commands to drive it. Dynamic types are not necessary for REPL-driven development.
- xapata 8y agoYes, that's why becoming happy with REPL-driven development is more important than becoming happy with dynamic typing. I think much of the productivity boost attributed to dynamic typing is in fact caused by heavy use of a REPL, and not dynamic typing.
- blandflakes 8y agoI'd maybe consider that perhaps becoming happy with REPL-driven development isn't a universal solution. It's not like I didn't understand the concepts, the tools, or the process. I just didn't find that it made me any more proficient at building software.
- kachnuv_ocasek 8y agoSome implementations of SML are definitely production-ready.
- plinkplonk 8y agowhich implementaions of SML are the production ready ones?
- udfalkso 8y agoAnother option worth considering is Elixir. It's also dynamically typed, but the pattern matching helps mitigate dynamic type issues if used correctly. It's a pleasure to work with.
- jb3689 8y agoI love Elixir but it's worth noting that I'd be hard-pressed to recommend it for every project because of the performance and base OS integration characteristics of the BEAM. The BEAM is a truly amazing piece of software and I do think that Erlang and Elixir are the true microservices dream we all wanted, but the BEAM uses preemptive scheduling and doing things like spinning up a new OS process can be a lot more work in Erlang than you'd deal with in other languages
- udfalkso 8y agoYup, it depends what you're building I suppose. For an Elixir/Phoenix web application or api I've found the performance to be very impressive.
- dnautics 8y agoWhat are the performance issues? People (e.g. Instagram) deploy scaled apps in Python, which is far less performant. Bleacher report obsesses about information push latency, and they use an all elixir stack that deploys to at least a million users. Not to mention WhatsApp.
- lostcolony 8y agoCPU bound loads won't benefit from Elixir. If most of what you're doing is a little minimal data transformation, but otherwise shuffling data to different network locations, and you want consistent latency/throughput, Elixir is a dream. If you're doing some tight number crunching, then not so much.
- dnautics 8y agoExactly. Who in sv is actually doing tight number crunching all the time? I think most of what we do is shuffling data around. If you need it, you can always also just orchestrate a container with a computational task anyways. And then someone's gotta marshal the data and metadata to that container... Might as well be something dreamy.
- zumu 8y ago> Main contenders so far are Haskell, Scala and Clojure (Reason might go on the list soon too) How about plain old OCaml? Jane Street seems to find some success with it.
- jwr 8y agoBackground: I have built and now maintain/extend a fairly complex application in Clojure/ClojureScript with 120k lines of Clojure code (for non-Clojure people: that is quite a bit, because of Clojure being so expressive). I think the whole "lack of static typing" thing is a red herring, for two reasons: 1. Large-scale code is more about contracts that specifically "static typing". There are many contracts which you can't express using a type system. 2. Clojure doesn't "lack" anything. You can implement contracts for functions using pre/post conditions, use clojure.spec to implement arbitrary contracts over your data (and I do mean arbitrary), or even go for core.typed if that is your thing. I started to rely on spec more and more and I believe that coupled with pre/post conditions, it is way more useful for larger systems than static typing. One other factor which is often forgotten in these discussions, because so few languages get there: please remember when "choosing" a language, that Clojure can be paired with ClojureScript on the frontend side. That means you can use the same data structures, same transport format (no JSON or XML quirks, ship EDN over websockets using transit and sente) and much of the same data model code on both sides. This is a huge advantage if you can make use of it. And guess what, your spec definitions work just as well on the client side, too.
- justinhj 8y agoScala is sometimes referred to as the Haskellator, in that once you get far enough down the pure functional programming road, you may find yourself wanting to switch to Haskell. Scala doesn't enforce pure functional programming as much as Haskell. On the other hand I believe that Scala is much more widespread in industry because it's easy to sneak it in the door as an OOP/imperative language that just happens to have good support for higher kinded types, immutable data and other fp features. My advice, if it's for your own personal learning and possibly for some small in-house projects learn Haskell. If you want to use it for a large project with developers that are interested in learning fp but not yet fully committed learn Scala. Also, yes, any dynamic language suffers from difficulties with refactoring, runtime errors, needing lots of test coverage and not scaling well to large teams and codebases. IMHO
- jwhitlark 8y agoAfter my time with Scala, I think that if I need a heavy type system, I'd look to Haskell. And there are certainly classes of problems that I would choose a type system, but the general case isn't nearly as strong as I though it was before seeing them used in anger. So, in the following, I'm not ranting at you, but I am saying I see lots of arguments for types that jump to QED without nearly enough evidence, with the "everybody knows" kinda explanation. And you happen to have put a handy list right in front of me when I had time to respond. > ... difficulties with refactoring, runtime errors, needing lots of test coverage and not scaling well to large teams and codebases. I was really impressed with Scala when learning or toy projects, but every time I saw it in production it was pretty horrid. Small, technical refactoring was taken care of by the IDE, but large scale refactoring takes the same amount of thought as before, but now there's 10x as much code to understand first. There were less runtime errors, but not much less, and I saw a lot of code that didn't error, but didn't get done what it needed to. When it comes to scaling to large teams, I saw lots of "The compiler says it's ok, and the tests pass, so commit!" where the code didn't make sense! The old thing about write code for programmers to understand first, and the machine to understand second... Outside of certain specific situations, I just haven't seen the benefits outweigh the increased code weight and slower time to market.
- 8y ago
- dmead 8y agoas a long time haskell user (but no longer) i disagree that haskell is production ready.
- dmead 8y agoclojure has an out of band type checker. have another look
- bgorman 8y agoI used to be a professional clojure developer, now I write in ReasonML (syntax change of Ocaml) for work. I would say that that the reason type errors do not hurt Clojure as much as say Javascript is because Clojure data is immutable, and all the common functions operate on collections or sequences. Basically, if you write idiomatic code, you constantly use datatypes that implement the same interface which higher order functions (map, filter, reduce, etc) expect. In contrast, in OCaml for example you have to think whether you are dealing with an Array or a List. Clojure also provides mechanisms to enforce that functions (Macros too with clojure.spec) are applied the correct data parameters (clojure.spec + pre/post conditions). Spec shines when you are integrating with "foreign" data like the results of a HTTP request. In my experience, it is faster to make changes to a project, then test quickly with the clojure repl than it is to spend a lot of time refactoring types. This is especially true when you are experimenting with how to process data a different way... Having to have all types work out at compile time is a real time sink, when mentally you already know how you want to process data.
- yogthos 8y agoWorked with static typing for about a decade primarily with Java in the enterprise. However, I've also used Haskell and Scala which have advanced type systems. I moved to work with Clojure about 8 years ago, and I don't miss types. If I did, I would've gone back to a typed language a long time ago. My experience is that dynamic typing is problematic in imperative/OO languages. One problem is that the data is mutable, and you pass things around by reference. Even if you knew the shape of the data originally, there's no way to tell whether it's been changed elsewhere via side effects. Keeping track of that quickly gets out of hand. What I find to be of highest importance is the ability to reason about parts of the application in isolation, and types don't provide much help in that regard. When you have shared mutable state, it becomes impossible to track it in your head as application size grows. Knowing the types of the data does not reduce the complexity of understanding how different parts of the application affect its overall state. My experience is that immutability plays a far bigger role than types in addressing this problem. Immutability as the default makes it natural to structure applications using independent components. This indirectly helps with the problem of tracking types in large applications as well. You don't need to track types across your entire application, and you're able to do local reasoning within the scope of each component. Meanwhile, you make bigger components by composing smaller ones together, and you only need to know the types at the level of composition which is the public API for the components. REPL driven development [1] also plays a big role in the workflow. Any code I write, I evaluate in the REPL straight from the editor. The REPL has the full application state, so I have access to things like database connections, queues, etc. I can even connect to the REPL in production. So, say I'm writing a function to get some data from the database, I'll write the code, and run it to see exactly the shape of the data that I have. Then I might write a function to transform it, and so on. At each step I know exactly what my data is and what my code is doing. Where I typically care about having a formalism is at component boundaries. Spec [2] provides a much better way to do that than types. The main reason being that it focuses on ensuring semantic correctness. For example, consider a sort function. The types can tell me that I passed in a collection of a particular type and I got a collection of the same type back. However, what I really want to know is that the collection contains the same elements, and that they're in order. This is difficult to express using most type systems out there, while trivial to do using Spec. [1] https://vvvvalvalval.github.io/posts/what-makes-a-good-repl.html https://vvvvalvalval.github.io/posts/what-makes-a-good-repl.... [2] http://danboykis.com/?p=2293 http://danboykis.com/?p=2293
- bribri 8y agoThis is the number one opposition to Clojure I hear. I was strongly in the static typing camp and rejected Clojure at first, even compared to Java and C++. I tried Haskell and found it amazing, but still too complex. After knowing more about functional programming I returned to Clojure and it finally clicked. I feel writing extremely simple code quickly and reinvesting that time into writing tests was a better workflow than any typed language I've used. I've only had a good experience in the REPL using emacs, but when you set it up properly, it's amazing.
- didibus 8y agoThat's my experience as well. Static types are overrated. Okay, so you caught the obvious errors a few minutes earlier, but while you were wrestling with types, I ran 30 quick tests validating the behavior, and made them unit tests. I've only ever had trivial type error bugs, all the nasty ones are behavioral or related to race conditions, or at the boundaries where types don't exist.
- CyberSun 8y agoHave been using Clojure for three years on a dev team of around 10 people. We haven't had any issue with maintenance and extensibility of the code base over time. I'd say it is as easy to maintain and extend as it was in the beginning. That said, we have a micro-service architecture. Thus the overall system grows horizontally, in that more and more components are built and developed. So individual components don't really grow that large. This would be different say to building a giant monolith like a AAA game, or a massive application like Photoshop. So I can't speak to how such a code base would scale. Also, this might sound strange, but I don't think we've ever had to refactor the code base in those three years. The paradigms of Clojure (data driven/functional/immutable/meta), combined with our architecture, it just doesn't really need you to refactor things to add features. Most adding of features tend to be... Write functions for it. Plug the functions inside the outer orchestration chains of function composition which dictates code flow. Add some more keys to our data-structures. Write tests. Release. Where as in Java, we used to have to shuffle all the Class/Interface arrangements around all the time to make room for new features.