6 ms·
I don't see clojure in the same realm here. Both rust and elm are geared more toward enforcing correctness through their type systems to tame complexity in larg
by keithasaurus 6y ago
I don't see clojure in the same realm here. Both rust and elm are geared more toward enforcing correctness through their type systems to tame complexity in large projects. In my experience, clojure, being dynamic, fits more as a comparison to vanilla JavaScript. In a langauge-to-language comparison, I think clojure is somewhat more appealing than JavaScript due to immutability by default and general functional niceties. However, the fullstack comparison is not quite apples-to-apples as clojure (backend) is JVM and clojurescript (frontend) is node. While it works for some people, clojure feels awkward to me as a fullstack language.
- nickbauman 6y agoTypes provide only the simplest most trivial kinds of correctness, though. And people routinely make mistakes with types.
- AlisdairO 6y agoThings like null safety aren't trivial for practical purposes IME.
- nickbauman 6y agoMost Optional implementations are kinda terrible, though. They cost more than they save. If I have a function with a param and I release it with it required, but later I change it to be optional (loosening a requirement) does rust require everyone calling that function the old way to change their code? If so that not an improvement!
- steveklabnik 6y agoIf you change a type from T to Option<T>, yes, all the other code has to change to take the option into account.
- nickbauman 6y agoYeah that's not good design. Lowering a requirement should not make callers complying to a stricter one have to change anything. But, ohhh.. right only the type changed. so everybody stop what you're doing and start over.
- steveklabnik 6y agoI strongly disagree, but this is why it's great we have a ton of languages! To me, forcing you to handle it is the exact point of an option type. The type wasn't the only thing that changed, the possible values have changed. You may be getting more values than you were previously. This means that some of your assumptions may be incorrect. Of course your code needs to change.
- skoodge 6y agoI might be misunderstanding, but I think you are talking about slightly different points here. It seems to me that the critique of an explicit Option type (that acts sort of like a box, in contrast to Kotlin's T? vs T) applies to when you pass in the Option as a function parameter to a function that previously expected it to always be T instead of Option<T>. In that case you as a caller are never "getting more values than you were previously", but you can now certainly pass in more values than you could before. Forcing callers to refactor their calls to use a new Option<T> type as a parameter simply amounts to a change in the type signature, but since the function is more liberal than before, it cannot break your assumptions (at least not assumptions based on the function type signature). (For what it's worth, I do find Kotlin's T? to be more elegant than the Haskell/Rust-style Option/Some type. But then again, Kotlin is not fully sound, so there's that. Dart's implementation of T? will be fully sound though, so there are definitely examples of languages going that route.)
- steveklabnik 6y agoThat is true! You're right that the perspective can be different. You could write <T: Into<Option<i32>> if you wanted, and your callers wont change. Frankly, using options as parameters is just not generally good design, so this issue doesn't really come up very often, in my experience. There are exceptions, but it's exceedingly rare.
- AlisdairO 6y agoWell, yes, of course. The thing you could previously rely on being present can no longer be guaranteed to be present - that _should_ require code in the calling function to change.
- nickbauman 6y agoNot if I loosened the requirement. Stricter adherents shouldn't have to change anything. This is poor language ergonomics.
- setr 6y agoTo be clear, you're talking about the function signature changing from fn(a: int) to fn(a: Option<int>) ? Technically, yes, all callers would have to update, but practically, you'd just define fn(a: int) { fn_2(Some(a)) } to avoid the breakage. That is, you're essentially telling the compiler how to loosen the requirements. Ergonomically, this seems rather fine. Especially if this means you gain (some) protections from the much more problematic case of restricting the requirements.
- mqus 6y agoThere is also the Option of accepting Into<Option<T>>, which does cover both variants and is completely backwards compatible.
- ithrow 6y agodoes rust require everyone calling that function the old way to change their code? If so that not an improvement! Can you share why is that bad? the compiler will tell you exactly where you need to make the changes.
- nickbauman 6y agoPoor ergonomics: I loosened a requirement. Nobody should have to change their code. Kotlin does this right.
- ragnese 6y agoHow often do you believe this really happens in practice? And does that truly outweigh the benefit of being able to define a precise contract on your APIs? How many times have you written a function and a version later said "Oh, wait. I guess I don't actually need that required Foo parameter! I used to, but now I don't!"
- bb88 6y ago> How often do you believe this really happens in practice? Regularly if you're doing refactoring of code. Otherwise code becomes unchangeable because it's too big of a burden once it's clear it needs to change. > And does that truly outweigh the benefit of being able to define a precise contract on your APIs? I would point you to the XML standards which allowed people to do exactly that, and instead JSON won.
- ragnese 6y ago> Regularly if you're doing refactoring of code. Are we talking about a published library or your internal-only code? If the former, I sympathize with the argument that relaxing a requirement should not force consumers to change their code. If the latter, then I find it much harder to sympathize. You're already refactoring your code, what is a few more trivial syntactic changes? You could almost do it with `sed`. > I would point you to the XML standards which allowed people to do exactly that, and instead JSON won. You know- this is an interesting point. And I guess I'm consistent because I absolutely hate JSON. I've only had to work with XML APIs very few times, but every time, it was perfectly fine! I could test my output against a DTD spec automatically and see if I did it right. It was great. JSON has JSON Schema, but I haven't bumped into in the wild at all. So it seems like "we" have definitely chosen to reject precision for... easy to read, I guess?
- ragnese 6y agoYou're basically just regurgitating a recent Rich Hickey talk. Interested readers can probably find it on YouTube. Rich is certainly a brighter individual than me, but some of his points are either him missing the point or being intentionally misleading. For example, he discusses how Either types aren't true sum types because Either<A, B> isn't the same type as Either<B, A>. So he disparages people who say that Rust/Scala/Whatever have sum types. He's missing the point because 1) All Either implementations I've seen have the ability to swap the arguments to match another Either with the types backwards, so it's a sum type in practice, and 2) Clojure has none of it, so why criticize the typed languages by saying their type systems aren't perfect when your language's type system isn't helpful at all? Throw the baby out with the bath water? To your specific point (which is also one of Hickey's), yes, it does kind of stink that loosening a requirement forces consumers to update their code. However, that minor downside does not mean that Optional is "not an improvement". It's still a HUGE improvement over Java's absurd handling of null (IIRC, Clojure is the same as Java there). Also, maybe changing something to optional isn't really "loosening" the requirements. It's just changing the requirement. If the parameter changed to optional, don't you want to be alerted to that? Why is it optional now? What will it do if I pass None/null? Maybe I actually would prefer that to the way I called the old version. It just never struck me as offensive to have to change my code when I upgrade a dependency. I have trouble sympathizing with that mindset. Edit: And what is the Clojure alternative? You can loosen requirements, but really, you never had enforceable requirements anyway. Is it apples to apples to talk about a typed language loosening its contract?
- uryga 6y ago> "Either types aren't true sum types because Either<A, B> isn't the same type as Either<B, A>." that's such a weird argument! did he also complain that tuples aren't true product types because (A, B) isn't the same as (B, A) ? why would they be the same, and not just isomorphic?
- ragnese 6y agoI haven't watched the talk recently, but my feeling at the time was that he was just being pedantic about the definition of a sum type. Kotlin's nullable types would be example of true sum types because they are symmetric. But you can only make a sum of `T + null` and not a more generic `T + U`. His real point, I believe, was that the `Either` implementations weren't as good as true sum types because of ergonomics. It's part of his philosophy/bias that type systems get in the way and therefore cause more harm than good. I don't really grok his point most of the time. It just feels foreign to me to not want as strong a type system as possible. But a lot of really smart guys feel that way: Him, Alan Kay, etc. I suspect that they're able to track much more stuff in their heads at a time than I am.
- deleted 6y ago[deleted]
- winstonewert 6y agoI think its worth noting that in Rust you can create a function that can take either Option or the value itself, if that is what you really want to do: https://gist.github.com/rust-play/b28257cd7c48d0f9e9b1893181aa99bc https://gist.github.com/rust-play/b28257cd7c48d0f9e9b1893181...
- nickbauman 6y agoThat is nice
- johnsoft 6y agoI think simple, trivial type systems offer simple, trivial correctness. Robust type systems can offer robust correctness (when used to their potential).
- jhomedall 6y agoIn addition to the benefits listed in the other comments, exhaustive checks in sum types are useful for catching unhandled cases, especially when refactoring.
- deleted 6y ago[deleted]
- keithasaurus 6y agoTypes still exist in clojure of course, you just have to spend time reasoning about what they may be in any given place. The longer I've worked with clojure, the less I've understood this argument.
- nickbauman 6y agoWriting your own type system in a Lisp is an afternoon task, but nobody does that because...
- deleted 6y ago[deleted]
- dragonwriter 6y ago> clojure (backend) is JVM and clojurescript (frontend) is node. Node is a backend JS implementation. ClojureScript is compile-to-JS and can run on the backend in Node or on the frontend in browsers.
- keithasaurus 6y agoThanks, that's correct. I often make the mistake of interchanging node for browser js on accident...