7 ms·
I will say this for Emacs, though: while bad interactions between packages do happen, they're surprisingly rare given the number and variety of packages that ar
by rcthompson 10y ago
I will say this for Emacs, though: while bad interactions between packages do happen, they're surprisingly rare given the number and variety of packages that are available.
- mpweiher 10y agoThat seems to be generally the case for the horrors of dynamic typing: rampant in the imagination, surprisingly rare in the real world. http://blog.metaobject.com/2014/06/the-safyness-of-static-typing.html http://blog.metaobject.com/2014/06/the-safyness-of-static-ty...
- progman 10y agoThis confirms my own experience. I discovered that programs written in (or use) dynamic languages like Lisp are surprisingly reliable (Emacs in particular). Type safety won't protect us from broken software. Quote: "The most common bugs caught by static typing are also the least critical sort of bug." Source: http://www.drmaciver.com/2016/10/static-typing-will-not-save-us-from-broken-software/ http://www.drmaciver.com/2016/10/static-typing-will-not-save... Static typing helps a lot to catch basic type errors but it is surely not the "Messiah" of code safety. In big systems the good old way of testing still seems to be the best practical way. Haskell's claim "If it runs then it's likely correct" is a deception because no compiler can catch logical errors. This was also Ada's problem in the Ariane disaster, although Ada has probably the strongest type system beside Haskell. You need special verification tools like FramaC, and even those tools don't catch all errors. Haskell's safety is even more questionable in face of the underlying libraries which are written in C. Finally, the still unresolved Cabal hell speaks for itself. Stack works only because it is an isolated repository where the maintainers have to take a lot of attention to make sure that new code doesn't break other code.
- mpweiher 10y ago> Static typing helps a lot to catch basic type errors > but it is surely not the "Messiah" of code safety. I've been wondering why that is, and one reason I can think of is that testing verifies values. This also implicitly verifies the types of those values.
- codygman 10y agoThe type system can verify values as well, but can do so more exhaustively than you can practically test.
- dllthomas 10y ago> Haskell's claim "If it runs then it's likely correct" is a deception This is missing some context. If we generate random functions until we find one that compiles, of course it's not "likely correct". But that's not what people are doing - they are setting out to write correct code. If the types they use exclude functions that are almost correct, then it's likely when they actually hit something that compiles it will also be something that's correct. All of that said, it's true that the claim is sometimes made more strongly than it deserves to be. > because no compiler can catch logical errors. Not without my help. But I can certainly write my code such that the compiler will catch certain logical errors I'm likely to make. I have a pile of examples, but not the time to elaborate - I'll add them later.
- btam 10y ago> I have a pile of examples, but not the time to elaborate - I'll add them later. Please don't forget to share, this sounds very interesting to me as a novice.
- dllthomas 10y agoAdded as a reply to my earlier comment :)
- dllthomas 10y agoExamples. The first few are in C, the last couple in Haskell because they need it. At the most simple, consider a function that I'm calling like `compute_thing(bid, ask)`. Maybe that's in error, and it actually expects `(ask, bid)`. If it was defined to take primitives (`int` or whatnot) then that's a logical error the types don't catch. But if I wrap those primitives in a struct to get nominal typing (`typedef struct ask { int v; } ask_t`), it's caught. This is only a small improvement. In code that's well exercised, this will probably be found by tests, and certainly you should make sure you have coverage. But an obscure corner case may only be run in tests where bid == ask, and then the values won't tell you anything's wrong. A bad value also doesn't tell you where the error is, whereas this use of nominal typing points you at the exact source location. (Named parameters would also catch this particular issue, where supported and used). Slightly more complicated, give the same treatment to indexes. `foos[idx]` versus `lookup_foo(idx)` with a nominally-typed argument. In the former case, there is room for me to have confused which index was talking about which array. For tests, this is possibly worse than the above case, because it's quite common for small tests to involve few elements, which means indexes are more likely to spuriously agree. Now let's get tricky. I had a few threads (statically assigned to various roles, not a pool) managing resources they own. Latency on the order of microseconds was vitally important, so couldn't have threads waiting around on locks. I had to be careful that a function called on one thread didn't touch a resource owned by another thread. If my access functions demanded a nominally typed token I passed between functions, I could detect exactly where I called something from an unintended thread. If this token took the form of an empty struct, all this checking happened at compile time with no runtime overhead at all (the exact same bytecode was emitted if I manually stripped out the tokens), and completely eliminated one kind of concurrency bug (which are often very difficult to detect and localize with tests). This made it very easy to move functionality between threads - I could make a small change and my compiler would point out specific locations incompatible with that change. Moving to Haskell, I had a Markov text generator, where the models were represented as a map from lists of strings to maps from strings to counts (`Map [String] (Map String Int)`). Merging two models was easy (`M.unionWith (M.unionWith (+))`) but if the two models looked back a different number of tokens then the result would be broken. I could hang that value on them as data, and check it manually... or I could carry it in the type and let the compiler check it for me. In a more serious context, I was evaluating SQL I had parsed. I would produce "I have evaluated this bit of it" results that I could thread together. Adding a phantom parameter to their type, I could distinguish between "this is something that operates at the table level" and "this is something that operates at the row level" and I could not merge them without them being of the same type. I then wrote functions to convert between the two. The one to take a "table" value to a "row" value (as one might when evaluating a sub-select in an expression) was a no-op. The one to go other way (evaluating an expression in a selection list or WHERE or ORDER BY or ...) I wrote to demand that I introduce the values present in the current row. So consider: I have lists and sometimes I forget to add them to my dictionary in the right places - a logical error if ever there was one. Now my type checker will catch it.
- rdnetto 10y ago> Static typing helps a lot to catch basic type errors but it is surely not the "Messiah" of code safety. I'd argue that type safety is only one benefit of strong types - the others being better documentation[1] and a design which is clearer and easier to reason about.[2] You can get both in a dynamically typed language, but they're much less common since the compiler doesn't require them. [1] I've lost count of how many times I've looked at some function in Python or Javascript and had no idea what kind of values I was supposed to provide. [2] My go-to example of this is how in Persistent (a Haskell ORM), values which have been inserted into the database (entities) and which have not have different types, since the non-inserted values don't know what their primary key is yet. This makes it much easier to reason about where the values are coming from and what needs to be done with them. > Stack works only because it is an isolated repository where the maintainers have to take a lot of attention to make sure that new code doesn't break other code. In practice, the only breakage checked for is compilation errors. The onus is still on library authors to declare compatibility bounds in their cabal files, which is why [PVP](http://pvp.haskell.org/ http://pvp.haskell.org/) is still a thing. I'm also not sure how accurate it is to call it isolated - it's pretty much at the centre of the ecosystem at this point.
- dllthomas 10y ago> I'm also not sure how accurate it is to call it isolated It's entirely inaccurate to call it isolated - you can trivially pull in other packages from hackage.
- codygman 10y ago> Haskell's claim "If it runs then it's likely correct" is a deception because no compiler can catch logical errors. Yes it can, see dllthomas' updated examples. > Finally, the still unresolved Cabal hell speaks for itself. The major reason for cabal hell was that you couldn't install multiple versions of the same package. This has been resolved with cabal new-build and should become the default after a few releases of testing. For the curious cabal new-build accomplishes this by copying what the nix package manager does. See: http://blog.ezyang.com/2016/05/announcing-cabal-new-build-nix-style-local-builds/ http://blog.ezyang.com/2016/05/announcing-cabal-new-build-ni...
- dllthomas 10y agoOne optics issue is that dependencies occasionally cause pain in every environment. When that happens in Haskell, it gets labeled "cabal hell" even though the worst issues have been long since fixed. https://en.wikipedia.org/wiki/Dependency_hell https://en.wikipedia.org/wiki/Dependency_hell
- saosebastiao 10y agoOf course in production software type errors are rare. They're generally the first errors that pop up during debugging or testing. Which means the primary benefit of a basic static type system is speeding up the feedback loop of finding all your type errors, and reducing the need for testing. The funny thing about that blog post is that the top 25 bugs "don't look like" type errors to the author, but almost all of them are type errors given some type system and type definition...and not just theoretical, but by existing languages and usages. Buffer overflow, for example, isn't a "type error" according to C, but it is according to Rust. As type systems become stronger and more advanced, more errors become type errors, which means you'll only recognize them as type errors if you use languages and constructs that make them type errors.
- mpweiher 10y ago> Of course in production software type errors are rare. Not according to static-typing fundamentalists. > They're generally the first errors that pop up during debugging or testing. Exactly, and dynamic languages tend to be highly interactive, whereas most languages with highly evolved static type systems tend to have very slow compilers. So the idea that the compiler for that language gives you useful feedback before your dynamic system gives you feedback from actually running the code is at best dubious. > ...but almost all of them are type errors I think you are confusing type errors with modeling errors, but that's very common.
- uglycoyote 10y agoHi... Static typing fundamentalist here. > So the idea that the compiler for that language gives you useful feedback before your dynamic system gives you feedback from actually running the code is at best dubious. It depends highly on how long it takes to run the code. I work on large pieces of software both static and dynamic which can take a long time to restart and test after changing the code. (far longer than the turnaround time for a static-language change-and-recompile turnaround). In the dynamic systems it frequently takes all day to make what should be a simple change because the turnaround time to test for simple errors is so lengthy and it requires multiple tries just to get it to "compile" (I use this word in a loose sense since there's no compiler but I say "compile" meaning types are correct, I'm calling functions that actually exist, accessing data members that actually exist, free of usages of undefined variables etc.). Not only does it take far longer to purge all of the errors from the newly written code in dynamic languages but I also find that when working with dynamic code that I didn't write myself, the lack of explorability (either manually or IDE-driven) hinders understanding of the code and therefore writing new code involves much more guesswork, which means I write far more incorrect code in the dynamic language. Once the code gets to the point where it is "production ready" it's usually free of these sort of errors -- whether it is written in a dynamic or static language. Otherwise, it would fail and by definition would not meet the label of "production ready". So I would tend to agree with the grandparent's statement that "in production software type errors are rare". The type of software that I work on though tends to be in house software that's a bit more bleeding edge -- it's constantly being adapted to be used in new situations, needing to be changed, and never quite meeting the label "production ready" in that it's not something that QA would approve of and we would ship to other users. For this kind of software, I would say that the parts written in dynamic languages have far more latent type errors than the static code. These a little landmines that don't occur in the everyday use of the software but users step on all the time when they hit edge cases that no coder had enough imagination to test for. In my own experience, writing code in a dynamic language can be faster than in a static language, but this usually falls apart around the point where the code gets to the point of being more than will fit on one monitor screen, or more than will fit in one person's head at any time. That's the point where in the dynamic language you start having to guess things, and in the static language the computer helps you avoid the guesswork. I would disagree with your point about slow compilers, because for smallish codebases static compilers are practically instantaneous anyhow, and for large ones the cost of compilation in still much smaller than the cost of one testing iteration of the application.