11 ms·
> The Rust or Haskell compilers, for example, insist on policing whatever private understanding you might have about the meaning of your code. The thing about
by ekidd 5y ago
> The Rust or Haskell compilers, for example, insist on policing whatever private understanding you might have about the meaning of your code.
The thing about "private understandings" is that they're just another way of saying "we expect ceaseless, flawless vigilance" (as one writer put it), not to mention "flawless communication and training."
Languages which impose weird restrictions tend to do so because it allows them to offer (nearly) ironclad guarantees about something else.
There are certain programming idioms that work brilliantly in Haskell. Those same idioms would be utterly miserable in the presence of unrestricted mutation.
Or to take the author's other example, Rust forbids shared mutable state, and it keeps careful track of ownership. But I can freely use native CPU threads without worrying about anything worse than a deadlock, and I can bang directly on raw bytes without worrying about anything worse than a controlled runtime failure. And this remains true even if team communication occasionally fails or if someone makes a mistake.
Sometimes I want to rule out entire classes of potentially dangerous mistakes, and not just have a "private understanding" that nobody will ever make certain mistakes.
As always, it's a matter of using the right tool for the job. If you need to write a high-performance, heavily-threaded network server that parses malicious binary data, Rust is a great tool because of those restrictions. If you need to do highly exploratory programming and invent new idioms to talk about your problem domain, Common Lisp is awesome. And if you need to build libraries where everything has a rigid mathematical structure, Haskell is a great tool.
In my experience, Commony Lisp is a deeply opinionated language, and its most dramatic opinion is that "your code and your data should have identical representations and structures." And for the right problem, that restriction is extremely powerful.
- tialaramex 5y ago> If you need to write a high-performance, heavily-threaded network server that parses malicious binary data, Rust is a great tool because of those restrictions. If dealing with Untrusted File Formats perhaps you should use a tool purpose-built for Wrangling them Safely, WUFFS. You won't find a Hello, World example for WUFFS because Hello World prints out text to your console, which is exactly the sort of nefarious stuff bad guys might try to do and so WUFFS doesn't even provide any mechanism you could use to do that even if you wanted to. But it does Wrangle Untrusted File Formats Safely, shrinking your remaining problem space. For example WUFFS would be appropriate for taking files your users claim are JPEG "photographs" they uploaded for their "profile picture" and turning each one into either a 64x64 pixel RGB array or an error without any risk that they seize control of your profile picture program and do goodness knows what else instead. Although Rust's memory safety means you can achieve confidence a "photograph" doesn't corrupt memory it doesn't require the rigour WUFFS brings to file parsing, so a Rust program could end up confused about unforeseen state while parsing the file. For example in Rust you can write a function that might mistakenly overflow a 32-bit integer and, in production it will just silently wrap. In WUFFS that function won't compile until you either decide explicitly what should happen (e.g. wrapping, saturation) for each overflow, or you trap all the cases where it could overflow as an error. This is very annoying of course, but we're parsing Untrusted File Formats and if we leave anything to chance that will be exploited.
- jdblair 5y agoI thought this post was one long gag until I found WUFFS on GitHub: https://github.com/google/wuffs https://github.com/google/wuffs
- scns 5y agoVERY good catch, was confused too.
- ekidd 5y agoYou're completely right, of course. I'm pretty confident that I can parse untrusted binary data in Rust with nothing worse than a denial of service. (And I have over a billion 'cargo fuzz' iterations to prove it.) But WUFFS is even more opinionated and strict than Rust, and so it can offer even stronger guarantees. Admittedly, it looks pretty obscure and the documentation is a little light, but it's a great idea. I am really just done with CVEs, or at least the sort of CVEs that appear in C programs. We know how to prevent so many classes of security holes completely, allowing us to focus on the harder challenges.
- duckerude 5y ago> For example in Rust you can write a function that might mistakenly overflow a 32-bit integer and, in production it will just silently wrap. In WUFFS that function won't compile until you either decide explicitly what should happen (e.g. wrapping, saturation) for each overflow, or you trap all the cases where it could overflow as an error. You can do this in Rust, up to a point. There's a lint to ban dangerous arithmetic: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic https://rust-lang.github.io/rust-clippy/master/index.html#in... . You can then use the {saturating,wrapping,checked,overflowing}_{add,div,mul,abs,...}() methods to decide exactly what should happen on overflow. But WUFFS seems a lot nicer. Judging by the README it first tries to determine whether overflow is actually possible, while Clippy will happily forbid you from running "1 + 1".
- amelius 5y ago> As always, it's a matter of using the right tool for the job. Nice in theory. In practice a manager will ask you to build A, then later they will ask you to add B and C, and these components should interact seamlessly of course. If you chose your language based on A, then you might get stuck on B and C.
- wyager 5y agoBusiness logic features are rarely constrained by the choice of language. All that really changes are probability of implementation error and ease of implementation. I’ve rarely had the experience that a particular business capability is easy in one language and hard in another language, but vice versa for a different feature.
- VRay 5y agoAre you mentally defining "business logic" as the 20 or so lines of code in a program that are completely independent of all input, output, OS libraries, and frameworks? Pretty much every app, driver, and framework I've worked on across the web, desktop, and mobile has been mostly glue holding different components together. Choosing a different language would require a nearly-complete rewrite in every case except for a tiny section of independent interesting logic (and of course, if you arbitrarily picked some meme language for your "business logic" you'd have to write more glue code to attach it to the rest of your software..)
- rightbyte 5y ago> Commony Lisp is a deeply opinionated language How so? You can do whatever paradgim you want in CL. There is no policing involved. Not in the sense of Haskell or Rust at least. CL will let you modify globals or share data willy nilly just fine. Mixing functions and values in lists is not really a restriction either, it opens up for all kinds of shenanigans.
- zetalyrae 5y agoThere is a fairly high bedrock of abstraction, mostly because the compiler is available at runtime, the blurring of the distinction between compile-time and run-time, and image-based development. Common Lisp programs can be as fast as Java, but the executables are huge because they have to bundle the entire image inside. And tree-shaking is less effective because the language is so dynamic it's hard to guarantee some code won't ever be called. And if the abstraction bedrock is high, then problem domains below that bedrock can't be approached with the language.
- lispm 5y agoContrary to popular believe, image support is common, but actually not required by the Common Lisp standard. Various Common Lisp implementations like ABCL (Common Lisp on the JVM), ECL, CLASP, mocl, ... don't support saving and starting images. > And tree-shaking is less effective because the language is so dynamic it's hard to guarantee some code won't ever be called. That depends on the delivery system. For example in LispWorks I can manually remove a lot of functionality: http://www.lispworks.com/documentation/lw71/DV/html/delivery-24.htm#pgfId-90015 http://www.lispworks.com/documentation/lw71/DV/html/delivery...
- zetalyrae 5y agoYes, LispWorks has a powerful tree shaker and I think SBCL now has one as well. Arguably images could get even smaller by distributing the runtime as a shared library. I admit this is an implementation detail.
- 5y ago
- marcosdumay 5y agoIt's all about thinking frameworks. Ensuring that effects are declared, that data has a static lifetime, or that your program is rewritable data are tools you can use to focus your mind in one part of the problem and less in another. Just like having irestricted access to everything your computer can do, or data + code encapsulation with multiple access levels. Not recognizing this (like in the article) is a clear sign of a single-paradigm developer that never really understood anything else but is too arrogant (insecure maybe?) to assume he doesn't understand something. So, the problem must be with everybody else. Anyway, a funny red-flag is that reaction to Haskell IO. People that never really learned Haskell tend to complain about IO with a deeper complaint of "why to I have to keep using monads?", while after people learn it they tend to complain about the lack of monad composition, with a deeper complaint of "why can't I use monads for everything?"
- arunix 5y agoCan you say more about the latter i.e. lack of monad composition ("why can't I use monads for everything") ?
- marcosdumay 5y agoAfter you learn how to solve states, input data, output data, error handling, parsing context, resource mocking, guaranteed resource release, and a lot of other things just by declaring the correct monad when you call your code, people tend to want to solve several of those at the same time. But for solving several of them, you need to compose your monads, and there is no good general way to do that. There are context dependent ways to compose some of them, but you are always thinking about their compatibility and they often require that you specialize your code.
- a1369209993 5y ago> But for solving several of them, you need to compose your monads, and there is no good general way to do that. It's (provably, I think) impossible for fully general monads, but for traversable-but-otherwise-general monads you can do[0]: newtype (%) (f1::k1->Type) (f2::k2->k1) (a::k2) = Comp { unComp :: (f1 (f2 a)) } instance (Monad f1,Monad f2,Traversable f2) => Monad (f1 % f2) where join (Comp a) = Comp $ a >>= (map join . mapA (unComp )) (Comp a) >>= k = Comp $ a >>= (map join . mapA (unComp . k)) (For that matter, you only need f2 to be traversable; f1 can be any monad at all.) 0: copied from my own code, so it might need other adjustment than s/map/fmap/ and s/mapA/traverse/.
- MaxBarraclough 5y agoI don't have anything in particular to contribute, but it struck me as interesting that the term private understanding is used here. It reminds me of the subtitle of one of the more well-known books on formal methods, The B-Book: Assigning Programs to Meanings. [0] Of course, using a formal methodology like B, ceaseless, flawless vigilance is mandatory. [0] https://www.cambridge.org/gb/academic/subjects/computer-science/programming-languages-and-applied-logic/b-book-assigning-programs-meanings https://www.cambridge.org/gb/academic/subjects/computer-scie...
- aidenn0 5y agoMy data is often in a class, structure, or hash table, all of which are vanishingly rare in my code...
- throwaway17_17 5y agoI’m curious, but my reading of your comment essentially boils down to ‘the actual data in my code is vanishingly rare’. Am I reading that right? Is the comment actually saying that in any given program most of the data is in some structure, but compared to the rest of the code is it almost insignificant? I’m not judging or contradicting, but if my reading is right, that’s the fist time I’ve heard anyone make that point in such a straightforward manner.
- erik_seaberg 5y agoLisp has a lot of functions for working with linked lists, which do double duty as functions for working with parsed code. But optimized Lisp tends to use structs and arrays and hashtables and avoids creating a lot of lists at runtime.
- aidenn0 5y agoI was replying to this: > In my experience, Commony Lisp is a deeply opinionated language, and its most dramatic opinion is that "your code and your data should have identical representations and structures." And for the right problem, that restriction is extremely powerful. Code in common lisp is almost entirely in a tree structure via nested linked lists. However, when programming in Common Lisp, I don't use this specific data structure very often for things that aren't code. Furthermore many other Common Lisp programmers write software this way as well, which is a bit of a counterexample to the assertion I was replying to. [edit] I think an opinion common lisp does have is that code should be easily manipulable as data. As much as scheme programmers like to call CL macros "unhygienic" it's far easier to write a correct general-purpose CL macro than a correct macro in other languages.
- coldtea 5y ago>Languages which impose weird restrictions tend to do so because it allows them to offer (nearly) ironclad guarantees about something else. Yes, but oftentimes this is more like: "If we tie you to this bed, you will never have a car accident! You'll also never get lost! This also makes it easier to stay healthy as we'll be feeding you the best food!" Sure, but I'll also miss all kind of activities I could be doing outside on my own. I'll also get bed sores. And I often want a steak or ice cream, even if it's not ideal, but you insist bringing me this organic gourmet vegan crap.
- tux3 5y agoI think that point of view assumes bad faith. No one sets out to create programming languages that tie people to their beds. If people add restrictions, it's not to make users miserable. It's fine if you like being unrestricted, I like that too. However, when you see people going out of their ways to add barriers to their own work, you should assume they reap a benefit that you're not fully appreciating, not that they hate fun and want to outlaw ice cream.
- myfavoritedog 5y ago"Bad faith" is too strong a term, but if you've spent much time in the Elm language sphere, you really will get what the original poster is getting at. https://elm-lang.org/ https://elm-lang.org/
- coldtea 5y ago>No one sets out to create programming languages that tie people to their beds. People can still end up designing such languages, even if they didn't set out with that specific goal. (Kind of like how you can bring war and division, even if your goal is to bring some religious peace on Earth or "the revolution"). >If people add restrictions, it's not to make users miserable. That's already explicit in my comment though. They didn't tie the person to his bed to make him miserable but to spare them from car accidents, to help them never get lost, and other such niceties! The misery is a by-product, not a design goal.
- throwaway17_17 5y ago
- nyanpasu64 5y agoRust does not "forbid shared mutable state". Instead it offers both the usual usual shared references along with a new unique reference, but restricts shared references so the type being pointed to must opt into aliased mutation. Making sharing and aliased mutation explicit in the type system allows both the programmer and the compiler to reason about which variables can change behind your back when calling functions or mutating through other pointers, and which ones are referentially transparent. (Though this property does come with sacrifices to programmer convenience, namely the restrictions on Cell, the runtime overhead and panic-proneness of RefCell, and the unsafe blocks associated with UnsafeCell. Hopefully GhostCell can do better.)
- mst 5y agoAlso, the article explicitly says that the rust compiler is usually right, with - IMO - the implication that haskell is likely usually right for its purposes too. But people have to get mad about the one throwaway comment at the top without even reading the next paragraph, let alone the rest of the article :(
- azundo 5y agoProbably good feedback for the author. People don't have time to read every article they come across fully so they're evaluating quality as they go. Throwaway comments like this will have the effect of losing audience so best to leave them out.
- lisper 5y agoThe thing is, if you start with Common Lisp, it's pretty easy to write a DSL that adds the constraints and provides the guarantees that you need. If you start with Rust or Haskell, it is impossible to remove the constraints those languages impose short of invoking Greenspun's Tenth Law and re-implementing big parts of Common Lisp.
- AnimalMuppet 5y agoI'm not sure that it would be "pretty easy" to write a DSL that adds the constraints and provides the guarantees that Rust does. I'm not sure that it's that easy to get it consistent and bulletproof. Yes, you can't remove the constraints of Rust or Haskell (other than using unsafe, I suppose). But if those languages have the constraints that you want, then trying to write that as a DSL instead is... probably hubris. Also wasteful.
- lisper 5y ago> I'm not sure that it would be "pretty easy" to write a DSL that adds the constraints and provides the guarantees that Rust does. It would be a lot easier than inventing Rust, for two reasons: 1. You don't need to write a parser. 2. The target language for your DSL compiler can be Common Lisp rather than machine language without sacrificing performance. Those two things make it absolutely certain that however much work it is to re-invent Rust in CL it will be strictly less work than inventing Rust from scratch. There are other benefits to this approach as well. For example: if, after implementing your DSL you discover that you've made a sub-optimal design decision somewhere along the line, it will be a lot easier to tweak your DSL than it is to tweak Rust.
- AnimalMuppet 5y ago"Easier than inventing Rust" is very much not the same as "pretty easy", though... And in a world where Rust already exists, in most cases it's easier to just use Rust than trying to write "that kind of thing" as a DSL in Lisp.