7 ms·
The idea of ubiquitous need for significant enforced correctness assurance is one of the worst things in software. Correctness is a resource like time or memory
by mlthoughts2018 7y ago
The idea of ubiquitous need for significant enforced correctness assurance is one of the worst things in software. Correctness is a resource like time or memory. Some applications need extremely high resource of correctness, some applications don’t (and can even fail badly, usually due to delay or rigidity, if they have an over-provisioning of correctness).
The presumption that every type of project should fundamentally begin with adherence to patterns guaranteeing high resource usage of correctness is a bad type of premature optimization / premature abstraction.
Rust & Haskell are great tools in the rare cases you do need extreme correctness. But in my experience most situations do not benefit from spending that cost (the ways Haskell & Rust enforce program structure is a huge cost) to get correctness, and you can get approximately just as much correctness with pretty low-touch unit & integration tests in most languages, without heavy costs of roundtrip compile times, static typing, forced (instead of selective) management of mutability and ownership, and so on.
- FullyFunctional 7y agoI doubt it will surprise you, but many disagrees with you. Using Haskell isn't a burden - on the contrary, it's far easier to get stuff working correctly. The type checker looks over your shoulder and stays out of the way until you make a blunder. It's not uncommon for things to work as soon as they pass the compiler. I work in C every day and this is not the typical experience there. I'm hugely interested in Rust, but I'm disappointed that they felt the need to rename all concepts and adopt an ugly syntax. I assume it was done to attract the C++ crowd.
- im_down_w_otp 7y agoProbably the other way around. When I first started using Rust it still had LWT and segmented stacks. I was excited that it seemed like I'd finally have a sort of "systems" Erlang. Over time the only consistent large nexus of Rust users using it "in anger" were the compiler team itself and the Servo team. Lots of those folks were, or had been, working on pretty significant C++ code bases, and over time their needs and perspectives seemed to (kind of necessarily) take hold.
- lewisinc 7y agoWould you say that's still the case? I've also been hoping that Rust would see a kind of Actor-oriented ecosystem emerge, and while there's definitely projects out there, none of them really support distributed computation in the same way you'd get from Erlang. At least yet, I'm still hopeful.
- nimih 7y agoAdditionally, at least in (GHC) Haskell, you can recover most of the flexibility typical of dynamic languages via use of undefined values, partial functions, the PartialTypeSignatures extension, and the defer-type-errors flag. Personally, I've found these tools to be very useful when prototyping, exploring/experimenting in a REPL, or beginning a refactor, and the process feels very akin to development without unit tests (or tests turned off/ignored/whatever), especially in the progression of incrementally adding back compile-time-checks and watching my mistakes surface.
- verttii 7y agoGreat advice, I'll try these features out while prototyping on Haskell.
- gdxhyrd 7y agoRust is designed to replace C++. That "ugly syntax" is the de facto standard in the industry. No one, comparatively, would use Rust if it went the Haskell/ML way.
- amelius 7y agoLots of people in the industry are using python, which has a syntax very different from c++.
- TurboHaskal 7y agoI’d say the syntax is pretty much the same, besides using indentation instead of braces for defining blocks.
- flomble 7y agoI find the point about "ugly" syntax interesting, because I'm generally quite indifferent to that sort of thing, but my experience tends to indicate that it's very important to people. Facebook created ReasonML, which is just a different syntax for OCaml (I think the AST parsed from it is interchangeable with one parsed from OCaml), purely because it's less alienating to people who are used to writing C-style code. I'd say though, that generally, people who are interested in "niche" programming languages are less likely to be put off by syntax than people who have only been exposed to the more mainstream languages, which is why their preferences are less likely to be the ones reflected whenever mass adoption is a goal of language designers.
- kccqzy 7y ago> and you can get approximately just as much correctness with pretty low-touch unit & integration tests in most languages You can get about the same amount of correctness in Haskell or Rust as you can in typical languages by omitting all unit tests. This is where people saw a certain sliver of Haskell or Rust community pursuing increased correctness with diminishing marginal return and assume they must represent all of the Haskell or Rust community. But didn't the same communities advertise their languages by that magical feeling of "if it compiles then it probably works"? Plenty of people use only simple language features, write no unit tests, and achieve a similar level of correctness as software in other languages with unit tests. But they aren't as visible because efficient boring software engineering doesn't get blog posts. Besides that, I actually totally agree with your first paragraph. Not all software needs to be totally correct. But my argument is that if you know you don't need total correctness, it will be less work to achieve a certain level of correctness in Haskell or Rust than in typical languages.
- atoav 7y agoThe “if it compiles it probably works”-tagline is certainly truthful in my experience, even without unit tests. I can imagine scenarios where you use Rust in a “non-rustic” way where this might not be true, but this goes against the grain of the language and is usually not very enjoyable to do. These correctness guarantees and tests have the nice side effect of helping you to isolate problems and writing bigger projects in Rust is for that reason alone more comfortable than say in python.
- senderista 7y agoIn my admittedly limited experience with Rust, writing tricky data structure library code, “if it compiles it works” was ludicrously far from true. That’s not to say that the type system didn’t help immensely with correctness, but what helped the most by far was property-based testing, which works just as well in dynamically-typed languages.
- atoav 7y agoIf you worked on a low level data structure library I’d assume your experience is quite normal, as you are not really using the type system as much — this however changes a lot if you program on a higher level. I use python for years now and I am convinced my python code has more errors than my Rust code, just because certain errors that Rust wouldn’t even compile are non-trivial to detect.
- deogeo 7y ago> the ways Haskell & Rust enforce program structure is a huge cost In my experience, it is the opposite of a cost - "worrying about types" doesn't slow me down. Instead, types help guide me, help reason about my code, and help documentation. Coding with types is, for me, faster than without. In fact, I find myself worrying about types the most in dynamic languages - I'm never sure what I can pass to a function, or what a certain thing is, so I have to manually review what a simple type signature or inferred type would tell me.
- domlebo70 7y agoI agree. People forget that you can't avoid types. It's the shape of the data. Whether they are static or dynamic/uni, you still have them, and you have to deal with them. I'd much rather deal with it upfront, statically, at compile time. I find it works much better for me, and it helps me model my program. The compiler is a tool I use to ask questions about my program, and we work together to build the shape together.
- jstimpfle 7y ago> It's the shape of the data. This is how far types are required to go. (And not even strictly - sometimes it makes sense to switch on the shape dynamically). The hype around types in contemporary languages goes far beyond mere shape. And that can lead to unnecessary program structure which can block further development.
- deleted 7y ago[deleted]
- mlthoughts2018 7y agoIf it was just simple type annotations, type synonyms for domain modeling, I’d agree. It’s not at all though. Just consider how extremely hard it is to set up systems of dynamic dispatch in Haskell. I do however think the “types help me reason about code faster” argument is a lot of bollocks. Years of experience working in large enterprise Haskell & Scala codebases made me believe this is not true at all.
- deleted 7y ago[deleted]
- im_down_w_otp 7y ago> and you can get approximately just as much correctness with pretty low-touch unit & integration tests This isn't accurate. It both vastly overstates the domain & value of unit tests and vastly understates what's possible to assure using types [1]. 1) https://blog.auxon.io/2019/10/25/type-level-registers/ https://blog.auxon.io/2019/10/25/type-level-registers/ disclaimer: I am the CEO & co-founder of the linked company, but it nonetheless shows some very powerful uses of Haskell/Rust-style types to provide assurances you couldn't get with even PBT, let alone simply unit tests.
- mlthoughts2018 7y agoCan you explain what parts of that article illustrate your counterargument? I don’t see anything in it that demonstrates overvaluing correctness from testing, and the domain modeling (eg Register) in the post seems juvenile and very unnecessary, certainly not creating any sort of expressiveness or correctness benefit, merely just some organizational preference of the writer of the code, which might even be counter-productive in the minds of people tasked to refactor it later.
- im_down_w_otp 7y agoRe: Unit Tests. To say that they're not exhaustive is an extreme understatement. You test the happy-path, maybe a few sad-paths you can think up, and if you're a really fastidious unit-tester, then you add future sad-paths you discover (often post deployment) to your test scenarios. They cover the narrowest slice of the input domain and the assertions are against the narrowest slice of the output domain. They're _just barely_ better than nothing for establishing some correspondence between requirements and implementation. One could graduate to property-based tests to attempt to do better than this by being able to specify a more global behavioral/constraint property that the implementation must uphold across all possible inputs. However, except in the case of really small state spaces, the input domain is still just a sampling of potential inputs (though perhaps many thousands rather than a small handful that would be represented by unit testing). You're probably 2-3 orders of magnitude closer to meaningfully establishing a correspondence between your requirements and your implementation this way, but purely by way of having created a kind of brute-force-ish automated generative unit testing system. Re: The article posted. The final API exposed to the end-user is very simple and fairly idiomatic for interacting with registers (the approach could be applied to other kinds of resources as well). It's no more, and probably even less, baroque for such a task than a typical C API, but with much higher assurances provided to the consumer of the API. The compiler is more-or-less enforcing correct access to the underlying hardware model, so that you don't do something dangerous in any random corner of your program, like incorrectly write a value meant for one register to another, flub a mask, or misalign a shift. The article shows a way to get strong assurances with pretty much no extra effort on behalf of a consumer of `bounded-registers` because the crate authors baked the assurances into the crate via types. How would you cover the domain of all possible combinations of register layouts, field layouts, field to register relationships and field values using unit testing and enforce those constraints from the bottom of the stack all the way to the top? How many unit tests would it be, and what assurances would you be able to claim, and how would you avoid the run-time cost of constantly checking and/or clamping values, and how could you compose those assurance claims with others further up the stack "by construction"?
- arnarbi 7y agoI don't agree, but I wanted to say to everyone else: Please don't downvote coherent comments where someone obviously put an effort into expressing their argument just because you disagree. This is perfectly valid discussion.
- pvg 7y agoThat's not how HN works. There's no 'don't downvote to disagree' rule. There is a 'don't go on about downvoting' one, though.
- solidsnack9000 7y agoBy the same token, tests and the “heavy costs” of testing round trips should also be considered resources.
- mlthoughts2018 7y agoYes, for sure. But unlike language features that enforce correctness, you can choose what and how to test to suit your use case. Haskell for example doesn’t support gradual typing. You must always pay the static typing cost regardless of whether you need it.
- solidsnack9000 7y agoGradual typing does allow a more graded trade-off between types and tests. Weak typing doesn’t really allow you to make that trade-off, since testing is going to be directed by failures and you don’t have the option to use types instead. Although in theory you have discretion with regards to tests, in practice you have to write a lot of them. Kind of like how interfaces and `Object` let you write less specific types in theory but in practice you end up writing them anyways.
- whateveracct 7y agohttps://typesandkinds.wordpress.com/2016/01/22/haskell-as-a-gradually-typed-dynamic-language https://typesandkinds.wordpress.com/2016/01/22/haskell-as-a-... I wonder if the extension proposed in this blog post would fit the bill and be attractive to certain non-Haskellers?
- tome 7y agoIt doesn't work. Tangentially, I had an interesting experience reading this blog post: * This is an amazing idea! Why did I never think of it? * Oh, it’s never actually going to work ... * ... and I said so much as a comment on the blog post three years ago
- whateveracct 7y ago
- hu3 7y ago> The presumption that every type of project should fundamentally begin with adherence to patterns guaranteeing high resource usage of correctness is a bad type of premature optimization / premature abstraction. Yes. Devs are often found on the four corners of the internet screaming things that could be extrapolated to: "Undefined Behavior is unacceptable regardless of the project!" "Garbage Collection is the devil!" "How dare you code in a language with nulls?" "Can you even sleep knowing your code might have race conditions?" Sure, I'd love to code in a stable language without nulls, no GC, no UB and no race conditions while still being easy to learn and quick to onboard to the average dev as C#/Java/Go/Python and having a large community with tons of libraries available. No such languages exist yet so tradeoffs have to be made.
- perl4ever 7y ago"Undefined Behavior is unacceptable regardless of the project!" This is an ambiguous statement as to whether it is about avoiding officially defined "undefined behavior" or about not programming in a language that has it. I agree that the former is impractical, but I think the latter is good advice. If a language defines something as undefined, and if we agree that permits nasal demons, then undefined behavior is unthinkable, intolerable, yet inevitable. It's a spiral into insanity, because this philosophy is self-contradictory and those who claim to believe in it do not believe in it at the same time. But if a language simply doesn't define everything, then nasal demons are off the table and we can all assume that we are reasonable people who are part of an advanced civilization that deals with exceptions and the unexpected on an adhoc basis.
- notzuck 7y agoDon't listen to this guy - he wants you to move your kids to another city because you have to be the only [insert minority here] on a team.
- whateveracct 7y agoIt's honestly not a big cost once you become an advanced user of it. I've been getting paid to do pure FP for like 5 years and the language rarely gets in the way. You can pick how statically correct you want to be, and refactoring in one direction or the other tends to be rather mechanical. And when it isn't, there's typically something interesting there anyways.
- marcosdumay 7y agoQuality does not waste time, quality saves time. That's a mantra from production engineering, but it applies just as well in any engineering activity, even when constructing abstract machines.