Why do I prefer Clojure to Haskell?

Поділитися
Вставка
  • Опубліковано 13 вер 2024
  • I prefer Clojure to Haskell. It's probably mostly due to accidents of history: getting in Lisp at an earlier age, spending more time with Lisp, and only having one Haskell job. But I do have some philosophical differences with the Haskell philosophy as it relates to Haskell as an engineering tool. In this episode, I go over four of them, and try to relate them with anecdotes.

КОМЕНТАРІ • 53

  • @sergeynosov1901
    @sergeynosov1901 4 роки тому +19

    Why do you prefer Clojure to Common Lisp or Racket?

    • @raymondhill7837
      @raymondhill7837 3 роки тому +5

      Clojure is actually useful

    • @raymondhill7837
      @raymondhill7837 3 роки тому

      @Jamie Walkerdine No it's not.

    • @TheAwesomeTool
      @TheAwesomeTool 3 роки тому +2

      @@raymondhill7837 CL is certainly useful, it has tons of libraries and the SBCL environment is blazingly fast. The problem is the usability of the system and the Common Lisp language itself which has grown too big its very difficult to bring new developers.
      Racket still is very useful, but not in a general-purpose way. It is very good at making DSLs for interfacing with complex, low-level systems. It's also very good at teaching programming to students.

    • @TheAwesomeTool
      @TheAwesomeTool 3 роки тому +6

      CL as a language lacks design. It is an amalgam of all the other lisp implementations of the 80's. Racket, on the other hand, evolved from Scheme and embodies it's functional approach to programming while still allowing programmers to bypass purity for practical reasons, the same as Clojure. The difference why Clojure took off and Racket didn't is because Clojure has access to the JVM ecosystem and all the Java libraries that already existed, IMO.

    • @AlexRodriguez-gb9ez
      @AlexRodriguez-gb9ez 3 місяці тому

      Clojure is standardized, Common Lisp is like perl with 100 ways of doing everything and no one picking the best way of doing things

  • @giladwo
    @giladwo 4 роки тому +19

    Disclaimer: I'm rather inexperienced with Haskell - haven't done anything more than playing around and reading a couple of books and a few libraries (mainly for fun, partially to get exposure to new ideas and use them in existing Python, Java & Elm codebases).
    However, my opinions about Haskell are different (reorganized according to your summary):
    1. Type soundness over pragmatism
    While I understand your argument, I think type soundness is more important in the long run. You can always open a PR with a missing feature, but improving type soundness as an afterthought is much harder. I also believe this will become rarer as Haskell becomes more popular.
    Regarding the libcurl example - I agree about many Haskell libraries being let projects, but I don't think only a "clojurist" would have added the quick-and-dirty solution. That's just a mindset difference, not a language-enforced one.
    2. Library evaluation requires understanding a lot of types
    If the library is complicated, the complexity has to be somewhere. I'd rather have it in the type level, because you get more guarantees at runtime etc. Therefore, I think the main issue is no documentation (or not good enough documentation), which is a much broader known issue.
    3. Type discipline transfers to Clojure
    That's been my experience as well (this applies to any other language - some just lend themselves more to foreign concepts than others). Can you think of a better way to learn these concepts other than learning Haskell?
    About the other direction - Clojure to Haskell - I think it's just as easy, you just need to keep in mind the suitable way to represent ideas.
    If Clojure's types are basically maps, you should use maps when trying to do the same things in Haskell (I'm aware there are more differences, but you get the point - Clojure types are closer to Haskell dictionaries than "proper" types).
    4. Types aren't designed for the real world (draft - document scenario)
    I don't have anything to add that I haven't mentioned in the previous bullets.
    Regarding the API changes - I believe the solution would simply be to add another endpoint with a Maybe instead of changing the old one.
    At the end, it boils down to personal preference.
    I hopefully believe that with more popularity Haskell's current issues will become much less significant (better documentation, more pragmatic & mature libraries, more idiomatic guidelines).

    • @ericnormand4210
      @ericnormand4210  4 роки тому +5

      Thanks for the comment. Great stuff.
      Re: #1, half agree. I would rather have both. The assumption is that they get the type right, which is a super strong assumption for a general-purpose library. I think they create more value by separating the two pieces out (minimal wrapper and typed options).
      Re: #2, half agree. Documentation is a huge issue across all languages. However, I don't think "the complexity has to be somewhere" is true. The complexity could be passed onto the library consumer while remaining type safe. In fact, this is probably the best option because you can't forsee all usages.
      Re: #3, maps in Clojure are heterogeneous, which is a pain in Haskell.
      Re: #4, yeah, adding new endpoints is always possible. Again, this is proliferation of types, when you play it out.
      It does boil down to personal preference. What problems are you more willing to deal with?

    • @matthiasschuster9505
      @matthiasschuster9505 4 роки тому

      @@ericnormand4210 1} I think with types is it the same as with immutabilty and all that kind of stuff.
      Imperative programmers also think they are the pragmatic ones.
      Right? And we should let a backdoor open. To fall back to 'simple' solutions.
      Isnt it the easy and simple solution here?
      Type safety being simple and pragmatism being easy?
      This is how it shines on me, at least for the moment..
      Thanks for your great channel and:
      Documentation first: This is also a simple way, in my findings.

    • @user-tx4wj7qk4t
      @user-tx4wj7qk4t 7 місяців тому

      ​@@ericnormand4210you might as well just program in scratch if you don't want a proper type system

    • @AlexRodriguez-gb9ez
      @AlexRodriguez-gb9ez 3 місяці тому

      Note that the problems with the types and staticness of Haskell is everywhere... You have to meticulously seperate pure and impure function and lift when neccessary. Haskell map was defined for lists, but later then they decided that actually maps should be for all containers/and they named the mapable containers functors despite they actually being endofunctors, not to talk about traversals and whatever...

  • @thegeniusfool
    @thegeniusfool 2 роки тому +1

    Same here.
    My stance is that my brain prefers Haskell, whereas my guts always go with Clojure. And, in the end, I *know* I can solve any problem -- modulo performance -- with Clojure.

  • @henninb
    @henninb 2 роки тому

    I enjoyed listening to your arguments. I am learning Haskell now. I am going to look at clojure

  • @cadetriestocode
    @cadetriestocode Рік тому +1

    Any opinions on ocaml, seems like a more chilled out Haskell?

  • @kuribas
    @kuribas 4 роки тому +5

    I agree with some of your criticisms, like that haskellers often care more about type safety than practicality. I find many libraries seem to be designed to scratch an itch, to prove a point, then are left unfinished and not very useful for practical use. They solve the problem quite well, but then fail on the more mundane parts. Even the most popular csv library cassava crashed on me with a vector index exception, without even giving a line number or stack trace. Other libraries also have such problems, for example the error handling in aeson is rather sub-par.
    However I don't agree that clojure is easier to evolve an API over time. I find most of the work of dealing with evolving programs and APIs is almost mechanical due to the type system, and making changes to dynamic (clojure, lisp, python, ...) languages involves much more trial and error. The dynamism argument is overstated IMO. Compilation is not a big deal, and I have flycheck running in emacs, which rechecks the buffer each time I make a change. I find that none of the problems Rich Hicky mentioned are real problems when programming in haskell. Changing types in haskell is very easy and quick. At worst they are a small annoyance, but the benefits of having Maybe by far outweigh the problems.
    I am a bit surprised your team didn't like your combinator approach, it seems more haskelly to me, and making a single function with the complexity inside the pragmatic way.
    Regarding documents being nothing more than a bunch of types, this is a documentation issue, and a proper documented library should have at least some examples which you can copy almost literally. Then for the details you can look at the types.
    Also, the quote isn't "avoid practicality at all costs", it's "avoid success at all costs". It means more that haskellers don't want to sacrifice expressiveness, new ideas, for practical concerns or familiarity, like the way Scala does, than try to be as unsuccessful and unpractical as possible. I'd say that haskell does try to be a practical language :-)

    • @ericnormand4210
      @ericnormand4210  4 роки тому +1

      Great ideas!
      I saw so many libraries that seemed to be people challenging themselves to type something. They did enough to solve their problem or learn that it was possible, they released it, then they abandoned it. I had to be careful of that kind of library.
      As for evolution of an API, it's true that the type system guides you to all the changes that stem from a refactoring. The problem was that much of refactoring and evolution is experimental. You don't actually know if your change is better until you do it. So the cost of experimenting is very high, because if you change a thing, you fix all the errors, then run it just to find out it's not better.
      Now, if you knew exactly the change you wanted to make, Haskell is way better than Clojure.

    • @kuribas
      @kuribas 4 роки тому +3

      @@ericnormand4210 It depends on how you write haskell code. I write many small functions which do just one thing, but do it well. I build functionality by combining more simple functions into complex behaviour. This way, even if there are big changes in the API, or requirements, I find I don't need to rewrite that much code. On the other hand, in clojure, I am more careful, and when I make changes I have to trace back the call graphs, in order to see that my code doesn't break anything. Even then it often does. I also need to keep the code simpler, as there is no type system to guide you. Tests may help here, but you still need to find where it bugs, where a type error shows you exactly the location things break. Also, In haskell I can easily have more complex logic, using many layers and higher order functions, and I know will be correct, because the type system ensures it. By the way, there is now an option to turn type errors into runtime errors, which could allow you to experiment more, even when the program isn't type safe. But I never really found I needed it.
      I really like how I can just start with any code, and I know when I need to make changes to functions or data types, the code will tell me exactly where it breaks. So I don't have to worry about getting it right from the first time. In contrary in lisp or clojure, I design more, because I know it will be hard to change things later.

    • @user-tx4wj7qk4t
      @user-tx4wj7qk4t 7 місяців тому

      Type systems are practical, they're literally the entire point of the program

  • @jujijiju6929
    @jujijiju6929 Рік тому

    This is very ASMR like... relaxing af.

  • @ruffianeo3418
    @ruffianeo3418 3 роки тому +1

    I dropped Haskell in favor of Common Lisp (SBCL rocks!). The main reason is that Haskell is too clerical to be practical. (Immutable Arrays, really?) Then there are at least 5 alternatives just for arrays (many of them having additional use cases they want to cover like STM or whatnot). In lisp you also have options but usually `(make-array ...)` does the job and you don't need displacement or whatever else is covered there. Same for strings. I wasted a lot of time trying to decide if, which kind of String (there are also at least 5 different namespaces for different string types) I would declare my default go-to string. And all that creates friction while working on the most basic of programming tasks. (One library wants a strict lazy BytesSring, the other a [Char], the next this type, the next that type. And in application code you end up writing conversions all the time.) This was the point where I restricted my Haskell usage to: Only if I don't need arrays and only if I need no libraries with more or less esoteric String types. At this point I started learning Common Lisp and I am happy ever since.

  • @georgH
    @georgH 3 роки тому

    Thank you for the video, it's always insightful and interesting to hear experiences from people with knowledge of two languages :)

  • @soanvig
    @soanvig 2 роки тому

    Regarding draft document and document - it was a correct way to solve this in ANY programming language. As those two things are not the same, and this usually comes up later on, after you are done with simple CRUD and requirements build up. That's about modelling the domain, not the types, so the example was not ideal

  • @darkengine5931
    @darkengine5931 3 роки тому +1

    I've often felt like statically-typed languages should be rather monolithic with respect to the number of primitive/intrinsic types they support, since otherwise, we get into problems like how you described where so many libraries get explosively complex in terms of the type scaffolding they build on top of the language as well as often having arbitrary gaps in functionality exacerbated by the type scaffolding. Coming from the C++ computer graphics end of the spectrum, I've worked in codebases with legacies dating to the 1980s where we had over 100 redundant sets of BLAS types, for example, such as 3-vectors, 4-vectors, 4x4 matrices, and quaternions, with explosive amounts of glue code to translate from one redundant representation to the next as well as enormous code duplication for the redundant operations defined for each redundant set. It's a standardization problem as I see it. There are a gazillion ways to design user-defined data types to represent some idea, and it's very hard to get the world to standardize on a small and sensible enough set without an authority figure like the language designer(s) providing the bulk of them.

  • @AK-vx4dy
    @AK-vx4dy Рік тому

    I'm not expert in either but i have years of pratical experience in systems integration.
    As I remeber from Rich talks, Clojure was straight response to type explosion and type overhead in production pratice (i agree with him firmly).
    As I understand closure has some mechanism of validation on input (spec?) wich handles checking in input is sound in declarative way or i am wrong ?
    Also Lisps (Clojure) have different kind of expresivness than haskell, both have great but from different angle.

  • @nathansire6623
    @nathansire6623 3 роки тому

    I'd rather have easy dependency management than the poetic compiler error messages that Haskell types give. Because I can decipher Clojure errors after working with it for so long.

  • @heck_fy
    @heck_fy 3 роки тому

    very interesting talk, thank you for sharing your thoughts!)

  • @bbzinhodomal2
    @bbzinhodomal2 4 роки тому +3

    When I do not know what I am doing, I prefer unityped. Example of it is in emacs, just want it to work and do not want to do any unit test.
    But the problem is when I want a program that works right, using the type system is faster than doing a lot of unit tests or run the entire application to see if it is working.
    How do you see if your code is right in both languages?

    • @ericnormand4210
      @ericnormand4210  4 роки тому +1

      Agreed. The type system is really great if you know what you need it to do. But often it's not the case. You're experimenting with APIs with varying qualities of documentation. The APIs are built with different type guarantees (try the WordPress JSON API). This is the context in which Clojure thrives. Of course, then once you do figure it out, it would be nice to button it down with types :)

    • @kuribas
      @kuribas 4 роки тому

      Interestingly, I find types easier when I don't know what I do, while I prefer to know what I do in a dynamic language. Because in haskell I just write down what comes in my mind right away, and then keep refactoring and reworking it until it solves the problem (and a bit more refactoring to make it elegant). For every step the type system guides me how to keep the code consistent. I can even keep trivial helper functions as typed holes, meaning I don't need to give an implementation, only the type. Then I work out the whole program, and when the result is good, I fill in the helper functions. In a dynamic language I know it will not help me with refactoring, so I try to design more before I actually write the code. I find it harder to refactor the dynamic code if it doesn't do what I want.

  • @dupersuper1000
    @dupersuper1000 4 роки тому +7

    Eric, what are your thoughts on OCaml (or it's syntactic transform, ReasonML)? Based on your arguments set forth here, it sounds like an ideal language for you, if I may speculate...
    You get a beautifully expressive type system (including idiomatic sum types), with support for type-classes via "module functors," with the ability to write "unsafe" imperative code whenever you need to. And even the imperative structures tend to lend you a hand in preserving type-safety--a good example is the "ref" type. Plus most of the time, the types can be inferred by the compiler.
    You also mentioned Rich's talk on the "maybe" type in Haskell. I watched it, and I remain ambivalent about the issue, mostly because converting a codebase to provide the extra "optional" type information can be done programmatically. But that's a moot point with OCaml, because you can always modify your public API functions to include extra "optional named parameters", which are internally represented as an "option" monad. The nice thing is that the argument's *omission* is interpreted as a "None" value for the parameter.
    Anyway, I'm interested in your thoughts...

    • @ericnormand4210
      @ericnormand4210  4 роки тому +1

      I don't have an opinion on OCaml. I've never used it. It seems nice, though!

    • @matthiasschuster9505
      @matthiasschuster9505 4 роки тому

      @@ericnormand4210 It's F# counterpart runs on the DotNet, which makes it more accessible to many jobs.
      Has no higher order modules dough, therefore super clean syntax and a lot quality of life changes to it.
      Love and adore this whole SML family, to which Haskell also belongs, OCaml is just a little grandpa like, also its community, less modern, simply put - in some areas.
      Fine in others.

    • @DenisG631
      @DenisG631 4 роки тому +1

      well the problem with Option/Maybe type is that it's not really union type due to the tagging issue. By this I mean for Option type is a set of all integers 0,1,2,3..., and None/nil. In Haskell or ML it's, however, Some(int) or None.
      Therefore changing the type from Int to Option on input, should not be a breaking change, since the set of valid input of a function is increased by 1 (None), so the callers, in theory, are not broken. Yet, if you perform this change you break all users of your API.
      E.g. in typed Racket, this is not the case -> docs.racket-lang.org/ts-guide/types.html#%28part._.Union_.Types%29

  • @eugenemosh3658
    @eugenemosh3658 2 роки тому

    Thank you! ♥

  • @CarlosSaltos
    @CarlosSaltos 4 роки тому

    Thanks for sharing Eric, very cool video !! ... of course all of this is a personal preference, but for me it helps a lot to make peace with the types or no types ideas and better land to the REAL WORLD ... meaning -> it does not matter if you use correctly or incorrectly types or even incomplete or no types, if what you try to do does not cover real cases and solves those real problems, it makes no sense (like the old libcurl wrapper that has a lot of cool types but at the end it does not cover the certificates option for solving a real world problem) ... so, again, thanks for sharing your experiences, it's very valuable

  • @AlexRodriguez-gb9ez
    @AlexRodriguez-gb9ez 3 місяці тому

    Clojure is for IQ 130 ppl, Haskell is for IQ 150 ppl...

  • @soanvig
    @soanvig 2 роки тому

    Quite interesting comparison. I don't know Closure though. But this is typical problem with strongly typed languages. And haskell libraries has few awesome and unique across all languages libraries. At the same time it most of the libraries are trash, and introduce weird syntax and require you to KNOW the library and it's syntax, operators and so on. Because each library in Haskell custom stuff, the Haskell look and feel is not defined by the language itself but by libraries which is very problematic when working in team or just having this long term

    • @soanvig
      @soanvig 2 роки тому

      Im talking about Curl example

  • @RobertKBell-nz4wx
    @RobertKBell-nz4wx 4 роки тому +2

    I feel like one seam between our preferences is somewhere around what "pragmatic" means or implies. E.g., in the "some int X is now an optional part of this API" example, yes it's pragmatic to be able to remain compatible with old callers, but it's also pragmatic to be able to know (and trim down) exactly what possible values you will have to deal with without having to know their life history.
    "Ok well if this API call came from a newer caller, then X might not be there, so I'll need to remember to check it dynamically" (A)
    VS
    "The code here says this value is of type X, so I know for certain I have an X; I don't need to backtrack how I came to have it" (B)
    It feels like the (A) path implies a large increase in work and/or possible defects; does it mean you don't have a way to say "I definitely have a value of this type"? (Sounds a lot like null-check hell...)

  • @LightProgramming
    @LightProgramming 4 роки тому +3

    Why not fork the project and add the backdoor or the missing functions as needed? Why was this not an option?

    • @ericnormand4210
      @ericnormand4210  4 роки тому +2

      That was an option. We looked at how actively it was used and updated. And we still didn't know if it worked. We decided to do what was known, which was shelling out to curl. We were already doing that elsewhere.

  • @MercurieVV
    @MercurieVV 4 роки тому +3

    Im not Haskell, but Scala. I never tried Closure by myself.
    But let me defend some Typed FP aspect.
    IMHO its odd that you didnt found flexible library. I see FP, is best suited for flexibility. On my Scala experience I dont remember cases that something werent flexible enough. You always have access under the hood. Maybe its because I use most pure Scala libs, instead of wrapper libs.
    What you are saying about transferring principles ClosureHaskel. I think its because Haskell is strict. But its for a reason - it wont allow you shoot in your leg, avoid lot of possible errors.

    • @nathansire6623
      @nathansire6623 3 роки тому

      Wouldn't a shell, service or API offer the most flexibility. I don't understand how a strict type system would help library dependencies be easier to consume? Your own code must line up with all the types in the library.

  • @dannytrieu6104
    @dannytrieu6104 3 роки тому

    Does FP requires Type?

  • @DenisG631
    @DenisG631 4 роки тому +3

    As much as I love Lisp (particularly Clojure) I think we need better static typing support and type inference to continue forward. Typed Racket (typed Clojure is it good enough? seems like nobody cares/uses it ;( ) is a good example. Type inference, union types, recursive types and all other goodies in Lisp
    docs.racket-lang.org/ts-guide/types.html
    I don't see how spec can be useful if:
    * it can't be inferred in majority of cases
    * it is only for runtime checks
    I still gravitate towards OCaml more, but will see where Clojure will go

  • @georgH
    @georgH 3 роки тому

    I understand your arguments on 'lifting' the validations to the compile-time type-level instead of having them as usual at the run-time value-level, but it does have its place in critical code.
    Check, for example, the experience at FB, how they managed to fix many undetected bugs they had with their previous systems by using types wisely: ua-cam.com/video/10gSoVZ5yXY/v-deo.html
    However, there are many situations were "good enough" is also ok, and you don't need all that added safety.

    • @randomseed
      @randomseed 3 роки тому

      Validation is one of the functions of a type system, however when you combine it with the basic quality of a type system which is completness you may end up with having very sophisticated (and precise) type system if the input data is diverse in their kinds (represented by different types when it's in). This data can appear on a boundary of a system, of a component or a unit (a function in FP). The problem which Eric highlights is IMHO rooted in a practical observation that the type system is not always the best tool for that kind of job because of its requirement of completness. So the Clojure answer to that would be to use spec for vaidation on a unit level and some custom validator (or spec-like library, like some coercer with validation support) for validation on an app boundary level. It does not automatically mean that using a spec will be less precise. You can write specs covering characteristics of data precisely but major difference is that they don't have to "know" about the rest of the world (like types).

    • @AlexRodriguez-gb9ez
      @AlexRodriguez-gb9ez 3 місяці тому

      @@randomseed You can also use macros to validate stuff, they are like types that run at compile time but they are turing complete and have the same syntax as the host language.

  • @jonaskoelker
    @jonaskoelker 4 роки тому

    I think you made the claim that when you make a parameter optional, the client code has to change in haskell. I think there's a way of avoiding that, if we're talking about functions:
    If we start out with this
    theEntryPoint :: Foo -> Bar
    theEntryPoint foo = doStuffWith foo
    we can change into this
    theEntryPoint :: Foo -> Bar
    theEntryPoint foo = theEntryPointWithOptionalFoo (Just foo)
    theEntryPointWithOptionalFoo :: Maybe Foo -> Bar
    theEntryPointWithOptionalFoo (Just foo) = doStuffWith foo
    theEntryPointWithOptionalFoo None = doSomethingElse
    That way preexisting callers of theEntryPoint don't need to know that anything has changed, and new callers can call theEntryPointWithOptionalFoo and get the new functionality.
    If you want the presence of fields to be optional everywhere you could Maybe all the fields and use that even for the code that requires all fields to be present, throwing an exception when they're not there. That sounds unappealing to me, but I think it would give you >80% of what similar Clojure code would give you.

  • @laughingvampire7555
    @laughingvampire7555 Рік тому

    -because you do not know how to code- because Java/C++ caused huge ptsd to everyone who used them and dynamically typed languages are a refuge. however modern typed languages have reduced the ceremony and annoyance. Type inference and a good LSP helps a long way, there is a talk of Richard Feldman on why Static Typing came back and it makes a lot of sense. Just watch it.
    I think those of us that love dynamically typed languages have to relearn typed programming and forget about Java/C#/C++ because those are bad languages anyways.

    • @AlexRodriguez-gb9ez
      @AlexRodriguez-gb9ez 3 місяці тому

      Haskell's type system although usually better than older languages can also be worse and more static...

  • @fredoverflow
    @fredoverflow 4 роки тому +3

    "avoid success at all costs"