13 ms·
It's nice to read a positive comment like yours occasionally, because the vast majority of the time, I'm just disappointed in how bad our programming tools (inc
by ragnese 5y ago
It's nice to read a positive comment like yours occasionally, because the vast majority of the time, I'm just disappointed in how bad our programming tools (including languages) are.
It's become a meme in my office that I'm the guy constantly bitching about how stupid our languages are. This week I was back on my soap box about the fact that almost zero mainstream (statically typed) programming languages can even let you write down that you want a non-empty string. In some languages you can implement your own class/type that wraps around the built-in string type, but most of the time you are now introducing memory and CPU overhead, can't use your type with other APIs that expect strings, etc. So nobody does that. But ask yourself this: how often have you ever written a function that requested a string as input and actually wanted an empty string? How many times did you not even think about what would happen if someone DID pass an empty string?
Same goes for positive and non-negative numbers. How many times did you write "int" when you actually only wanted a positive number? If I have to type "if x <= 0 throw FooException" as the first line of one more function, I'm going to scream. (A lot of languages do have unsigned ints, to be fair. But some still don't, or their unsigned ints are almost useless.)
People make all kinds of Stockholm Syndrome-y excuses for it ("validate inputs at the edges is all you need"), but the truth is that (most of) our languages are so deficient for expressing really basic ideas.
Thank goodness there are some languages that do try to make it possible to write "newtypes" and try to make concurrency safer. Now if we could just get everyone to adopt those in less than a decade, maybe we'd be able to get to the next generation after that, and then maybe we'll have good programming languages before I die.
Sorry that turned into a rant... :/
- raspasov 5y agoCheckout Clojure spec for a very expressive way of defining data requirements. It allows you to use arbitrary functions to describe data requirements. That way you are not limited by static, compile-time only descriptions of data flowing through your program.
- ragnese 5y agoI used Clojure on a project while spec was still alpha/beta or something, so I never used it. It does sound interesting, but I'm skeptical. Even the way you described it- I'm still just writing a function to validate my data, aren't I? Is that truly any different than just calling `validateFoo()` at the top of my functions in any other language?
- eckesicle 5y ago'Maybe Not' is a great talk on this subject matter. https://m.youtube https://m.youtube .com/watch?v=YR5WdGrpoug
- scns 5y agohttps://m.youtube.com/watch?v=YR5WdGrpoug https://m.youtube.com/watch?v=YR5WdGrpoug
- raspasov 5y agoThere's more power than that in spec. For example, you can globally define :address/zip to be a string that's more than, say, N characters long. Now anytime you encounter an :address/zip, regardless of whether it is inside, say :billing-address or :shipping-address dictionary/map, it can be checked for those function predicates.
- ComodoHacker 5y ago>how often have you ever written a function that requested a string as input and actually wanted an empty string? To be fair, I do it quite often. Most of the strings I deal with in my code are coming from user input, and most of them are optional. They are usually just passed to/from a database. If the string has some internal meaning (like ULSs or file paths), it usually gets wrapped in an object anyway. If you're processing some formal language or network protocol, that's another story.
- ragnese 5y agoLet me ask you this, though. If your strings that come from user inputs are optional, doesn't that mean they could also just not be present (as in null)? Why do you need or want two different ways to express "nothing"? Are all of the text fields just funneled right into the database without checking/validating any of them? I've written a number of RESTy/CRUDy APIs and I can't count the number of "check that username isn't empty" checks I've written over the years.
- feoren 5y agoThe argument for disallowing nulls is much stronger than the argument for demanding a compiler-enforced non-empty string. I definitely support the ability to declare variables, including strings, as non-nullable. An empty string is simply analogous to the number 0. It doesn't really overlap in meaning with null. It's true it would be useful to occasionally disallow the number 0, but only very occasionally. The obvious example is division, but having a representation of +/- infinity alleviates some cases. > I've written a number of RESTy/CRUDy APIs and I can't count the number of "check that username isn't empty" checks I've written over the years. Paraphrasing: "I've written the same kind of method over and over throughout my career and have been unable to (or made no attempt to) abstract it away." I love strong type systems, but it doesn't sound like the type system is your problem here. The problem is that you're constantly re-implementing the same business logic.
- ragnese 5y ago> The argument for disallowing nulls is much stronger than the argument for demanding a compiler-enforced non-empty string. I definitely support the ability to declare variables, including strings, as non-nullable. An empty string is simply analogous to the number 0. It doesn't really overlap in meaning with null. It's true it would be useful to occasionally disallow the number 0, but only very occasionally. The obvious example is division, but having a representation of +/- infinity alleviates some cases. Well, of course you should be able to declare something non-null. What do I look like, someone who likes Java? :p My point wasn't that I WANT to use null instead of an empty collection/string, it was that our languages give us multiple mechanisms by which we can pass in "nothing" for strings/collections, but they give us zero ways to ask for a non-empty string/collection. That's super frustrating! Yes, of course "null" and "empty set" are technically and semantically different. But they're close enough that you could actually deal with having only non-empty strings + nulls and be able to mostly express what you want. That's not the case if I actually want a non-empty string in many of today's languages. Not if I want it to be usable with other APIs and the standard libraries, that is. > Paraphrasing: "I've written the same kind of method over and over throughout my career and have been unable to (or made no attempt to) abstract it away." I love strong type systems, but it doesn't sound like the type system is your problem here. The problem is that you're constantly re-implementing the same business logic. Eh, no. I haven't worked in the same language or on the same project for my whole career. So, yeah, I've noticed that I'm pretty much always either defining a bunch of boilerplate types up front or I regret not doing it when a 0 hits the database because someone wasn't careful with their math or had an off-by-one error. Maybe I should publish a book a la Gang of Four and call it "Static Type Patterns". ;) So, yeah. Believe it or not, I HAVE implemented stuff like PositiveInt and NonEmptyString a bunch of times in a bunch of languages. And it's better than not having it, but it still sucks because in several of those languages, it means that I have all kinds of noise converting to and from, e.g., the native string type. And that's because most of the above languages have no concept of "newtypes" and have no intention of letting programmers define or refine "primitives". It's not really about "business logic". It's about "I know how to describe the shape of this data, but my statically typed language won't let me."
- hota_mazi 5y agoMmmh... these are not very convincing examples. Statically typed languages give you u8/i8 types of numbers. Maybe having a non empty string, or non empty list, type is useful now and then, but in practice, just have your code work just as well on both empty and non empty values, and you're good to go. I'm pretty happy with the languages we have today overall (Kotlin and Rust at the top, but C#, Swift, and Java get an honorable mention).
- AnimalMuppet 5y agoSome statically typed languages give you u8/i8. Java doesn't, despite it being one of your "honorable mention" languages.
- ragnese 5y agoYour comment kind of galvanizes my view that most of us suffer from Stockholm Syndrome with respect to our programming languages. As another commenter said, some statically typed languages give you unsigned numbers. Maybe most of them do. But definitely not some of the most popular ones. And out of the ones that do, they are often really unhelpful. C's unsigned numbers and implicit conversions are full of foot-guns. Java basically doesn't have unsigned ints. It does kind of have this weird unsigned arithmetic API over signed ints, but it's really awkward and still bug-prone to use. Kotlin's unsigned numbers API is very poor. Very. Kotlin does not give a crap if I write: `Int.MIN_VALUE.toUInt()`, so it's perfectly happy to just pretend a negative number is an unsigned number. Not to mention that unsigned number types are implemented as inline/value classes which don't even actually work correctly in the current version of the language (just go look at the bug tracker- I literally can't use value classes in my project because I've encountered multiple DIFFERENT runtime crash bugs since 1.5 was released). It, like Java and C, etc, is perfectly happy to wrap around on arithmetic overflow, which means that if you didn't guess your types correctly, you're going to end up with invalid data in your database or whatever. Rust and Swift have good unsigned number APIs. Notice also that I didn't say anything about non-empty collections. Yes, I absolutely want non-empty collection types, but that is no where NEAR as important and useful as non-empty strings, even though they might seem conceptually similar. I'm willing to assert, like the bold internet-man I am, that you and all of the rest of us almost NEVER actually want an empty string for anything. I never want to store a user with an empty name, I never want to write a file with an empty file name, I never want to try to connect to a URL made from an empty host name, etc, etc, etc. It is very often perfectly acceptable to have empty collections, though. It's also very frequent that you DO want a non-empty collection, which is why we should have both. We don't even need empty strings (or collections, really) if we have null. You say you like Kotlin and Rust and I work with both of them extensively. I can point out a great many shortcomings of Kotlin in particular. I used to be enamored with it, but the more I use it, the more shortcomings, edge cases, bugs, and limitations I run into. Rust is pretty great, but even that has some real issues- especially around how leaky the trait abstraction is. But at least Rust's excuse is that it's a low-level-ish systems language. It's these "app languages" that irritate me the most.
- taeric 5y agoI'm not so sure that I sympathize with your example. Why not a type for even numbers? Odd, prime, not-prime, etc? You really are asking for a type that is "valid data." Commendable, but not a static property of data. As a fun example, what is a valid email address? Once established as valid, how long will it stay that way? If invalid, how long until it can become valid? Do I think better typing can be a boon? Absolutely! Can it also be a burden? Absolutely!
- DennisP 5y agoDependent typing can do that sort of thing. In fact, here's a stackoverflow answer that uses even numbers as an example of a dependent type: https://stackoverflow.com/questions/9338709/what-is-dependent-typing https://stackoverflow.com/questions/9338709/what-is-dependen...
- taeric 5y agoRight. And I look forward to advances in them. I can count how many times this would have actually helped my program, though...
- exdsq 5y agoWhy do you think that? It might require a different mental model of types in order to see the benefits but I can't believe anyone is working on a project that doesn't have any sort of properties or 'business' logic that dependent types wouldn't help encode. Have you read anything about type-driven development? https://blog.ploeh.dk/2015/08/10/type-driven-development/ https://blog.ploeh.dk/2015/08/10/type-driven-development/
- taeric 5y agoI didn't mean for that to read as dismissive as it clearly does. Should have waited till I had more to say. :) I don't think the number of places dependent typing could have helped me is zero. Just like linear typing is nice. I just know that many of the dependencies I work with are much more dynamic in nature, such that getting them in static types has tough ROI to justify. Bluntly, the hardest program failures I've ever seen have been second systems from people learning generics and higher kinded types. I'm convinced these techniques are worth learning. I'm not convinced that they provide the immediate return on effort that they are usually sold with.
- kwertyoowiyop 5y agoIt’d also be handy to put other limits on my numeric variables, for instance to automatically throw an exception if an angle delta goes out of the range -180 to 180, or whatever.
- Jtsummers 5y agoYes, it is handy. This is why I like Ada (or the idea of it, it's rarely been used in my work because it's hard to sell others on) for safety critical systems. With SPARK/Ada you can even work towards proofs that your code won't assign outside of that range so that you don't end up with runtime exceptions.
- WalterBright 5y ago> almost zero mainstream (statically typed) programming languages can even let you write down that you want a non-empty string. Today D will fulfill your dreams! struct MyString { private string s = "error"; alias s this; this(string s) { assert(s.length); this.s = s; } } void main() { MyString ms = "hello"; test(ms); MyString mserror = ""; // runtime assert fail } void test(string s) { }
- Leherenn 5y agoWould it be possible to have a static assert? In that case I would expect it to fail at compile time, not run time.
- WalterBright 5y agoYou can use static asserts if you turn the constructor into a template and pass the initializer into a compile time parameter.
- mhh__ 5y agoNo. If D had unlimited access to C code at CTFE then maybe you could implement refinement types at compile time as a library but for now you are stuck with what Walter has posted. I implemented a sketch of the API for this ages ago but the actual type checking had to be done at module load time because was of aforementioned (justified) restrictions to CTFE Dependant types are a whole other monster.
- ragnese 5y agoI love that! So it's the `alias s this` that's the magic here, I assume? It's making the `this` pointer actually point to the stored field? That's awesome!
- amelius 5y ago> the vast majority of the time, I'm just disappointed in how bad our programming tools (including languages) are If you are disappointed in programming tools, you didn't see hardware design tools yet.
- sharikous 5y agoHave you tried Ada? Its type system is the closest I could think of to your use case?
- ragnese 5y agoI haven't, but I've done a fair amount of reading about it and that is the kind of thing I'm talking about.
- still_grokking 5y agoYou're looking for refinement-types? Scala to the rescue: https://github.com/fthomas/refined https://github.com/fthomas/refined And a new one: https://github.com/Iltotore/iron/ https://github.com/Iltotore/iron/ The latter will be zero overhead once a compiler ticket is closed. Both solutions will yield compile time checks where possible, and fall back to runtime checks when the data isn't statically known.
- still_grokking 5y agoRelated to refinement-types is the concept of "parsing, not validating": https://news.ycombinator.com/item?id=21476261 https://news.ycombinator.com/item?id=21476261 https://news.ycombinator.com/item?id=27166162 https://news.ycombinator.com/item?id=27166162
- ragnese 5y agoYeah, pretty much. I know such things exist on the periphery, but I think it's extremely disappointing that we keep churning out "new" languages that are basically "Here's C again, but with one interesting feature from the 70s!" I worked on a Scala once several years ago and I really didn't "get it". The more I use Kotlin, the more I wish it were Scala.
- lamontcg 5y ago> How many times did you write "int" when you actually only wanted a positive number? subtracting one from zero and getting max_uint can be its own brand of fucking horribly awful. having the language itself throw in that circumstance can also be exactly what you don't want.
- Jtsummers 5y agoIn some languages you can get a choice. Broken record time, but Ada: type Byte is unsigned 2**3; This will permit any value in the range [0,7] and when you exceed it (in either direction) it will wrap around (the desired action if you choose this type). In contrast: type Byte is range 0..7; Will give you a runtime error when you exceed the bounds by trying to increase beyond 7 or decrease below 0. Having this choice is nice, you get to decide the semantics for your system.
- still_grokking 5y agoAre that static or runtime checks (or both)?
- Jtsummers 5y agoDefinitely runtime, potential for compile time. The compile time checks will at least prevent obvious cases (assigning a value out of the range like using either Byte type above: T := -1 will get a compile time error). Using the second Byte type, paired with SPARK/Ada, this bit of code should set off the proof system and prevent compilation: procedure Foo is type Byte is range 0..7; T : Byte := 0; begin T := Byte - 1; end Foo; (Not that that's useful code, but a basic example.) That shouldn't make it past the SPARK/Ada system to compilation. Now change it to this: function Foo(T : Byte) return Byte is begin return T + 1; end Foo; and SPARK/Ada should warn (been a bit, but should also fail to compile) that this could cause overflow.
- lamontcg 5y agoYeah I took Ada for CS 210/211 back in 1991, are we there yet?
- deleted 5y ago[deleted]
- mtreis86 5y agoCommon Lisp can do that CL-USER> (defun non-empty-string-p (string) (and (stringp string) (plusp (length string)))) NON-EMPTY-STRING-P CL-USER> (deftype non-empty-string () `(satisfies non-empty-string-p)) NON-EMPTY-STRING CL-USER> (typep "" 'non-empty-string) NIL CL-USER> (typep " " 'non-empty-string) T
- scns 5y agoMumble, Greenspun, smthing somethng
- ElFitz 5y agoPerhaps via some monad, like Swift optionals? Unless it isn’t a monad and I still haven’t understood those?
- mbrodersen 5y agoRefinement Types is the answer. See Liquid Haskell, F*, Dafny etc.