9 ms·
I strongly disagree with this notion of "simplicity" as being attributable to scarcity of language features. Some of the languages that I felt were the easiest
by knucklesandwich 11y ago
I strongly disagree with this notion of "simplicity" as being attributable to scarcity of language features. Some of the languages that I felt were the easiest to use had quite a number of language features, but had simple semantics. I think Rich Hickey nailed this in his "Simple Made Easy"[1] talk. Complexity is not about additivity, it's about entanglement.
[1] http://www.infoq.com/presentations/Simple-Made-Easy http://www.infoq.com/presentations/Simple-Made-Easy
- bjwbell 11y agoHow do you have a large set of language features with them not interacting? In Java, serialization and generics interact with practically everything. In C++, RAII interacts with exceptions, which is the point but isn't exactly pleasant.
- knucklesandwich 11y agoGenerics solve an occurrence of too much entanglement. That is, it solves entanglement of an abstract "shape" of computation with a specific set of type definitions. Generics actually allow you to not think about an additional dimension of your program (i.e. the exact types a computation or data type can be used with). Haskell programmers famously point this out with the observation that a generic fmap is safer than one that has knowledge of the concrete types it uses. The type signature of fmap is this: fmap :: Functor f => (a -> b) -> f a -> f b In practice, what this means is that you can be assured that your fmap implementation can only apply the passed function over the value(s) wrapped in the functor, because of the fact that it cannot have visibility into what types it will operate on. In golang, because of a lack of generics, you can write a well-typed fmap function, but it will inherently be coupled with the type of the slice it maps over. It also means the author of such a function has knowledge of all the properties involved in the argument and return type of the function passed, which means the writer of an fmap can do all kinds of things with that data that you have no assurances over.
- catnaroek 11y agoExactly. Parametricity is the killer feature of statically typed functional languages. This why it saddens me when Haskell and OCaml add features that weaken parametricity, like GADTs and type families.
- js8 11y agoCan you elaborate on your last sentence?
- catnaroek 11y agoSorry, for some reason the “reply” link didn't appear below your post until after I had written my reply to Peaker. My reply to you is exactly the same: https://news.ycombinator.com/item?id=10668568 https://news.ycombinator.com/item?id=10668568
- Peaker 11y agoHow do GADTs or type families weaken parametricity?
- catnaroek 11y agoWithout either GADTs or type families, two types `Foo` and `Bar` with mappings `fw :: Foo -> Bar` and `bw :: Bar -> Foo` that compose in both directions to the identity, are “effectively indistinguishable” from one another in a precise sense. If you have a definition `qux :: T Foo`, for any type function `T` not containing abstract type constructors, you can construct `justAsQuxxy :: T Bar` by applying `fw` and `bw` in the right places. With either GADTs or type families, this nice property is lost.
- lomnakkus 11y agoHowever, GADTs and TFs are completely opt-in, so it seems a bit of a stretch to construe this as a generally bad thing. IME it's not as if library authors are arbitrarily (i.e. for no good reason) using GADTs or TFs instead of plain old type parameters in their APIs.
- i_s 11y agoMost people consider garbage collection to be a net win in terms of simplicity. Have you thought about why? Not every feature interacts with other features in complicated and error prone ways.
- catnaroek 11y agoYou mean “ease of use”, not “simplicity”. Simplicity is the lack of (Kolmogorov) complexity.
- bjwbell 11y agoThat's why I said large set. I haven't thought about garbage collection enough to have any insight on it.
- kevinr 11y agoI think the politest description I can provide of the experience of tracking down GC bugs is that they interacted with other features in complicated and error prone ways.
- i_s 11y agoBut was that code in the GC implementation, or your program? Because if its in the implementation, then that is a different matter. We have to distinguish between simplicity of implementation vs simplicity provided the user. I agree that if it is not implemented correctly, it can be a net loss in simplicity.
- kevinr 11y agoIt was code in my program.
- jcrites 11y agoI believe that garbage collection is a net win because it allows software to be composed in simple ways when it would otherwise be difficult to compose. I can pass data from one part of the program to another without coordinating both parts to respect the same memory management convention, and without having to pass that information from one place to another. This makes it easier to compose software, and in particular to reuse software like libraries (that frequently end up as layers between one component and another). For a concrete example, in a Java program I can simply publish an event into a Guava EventBus [1] without worrying where it will end up at the time I write that code. There's no real risk that I'll end up with a memory leak. I can connect two things together that weren't designed to be used together, and I can do it while inserting intermediate layers that transform, copy, record, measure, that data. Garbage collection significantly reduces the amount of coordination necessary between unrelated parts of the code base, thereby improving code reuse. This is what I would claim is less commonly recognized win, beyond the more commonly recognized wins from eliminating classes of obvious mistakes. EventBus is just one random example that involves plugging things together - the same effect is present all over Java libraries, from logging frameworks to collections to concurrent data structures. [1] https://github.com/google/guava/wiki/EventBusExplained https://github.com/google/guava/wiki/EventBusExplained
- catnaroek 11y ago> How do you have a large set of language features with them not interacting? The ability to write interesting programs in a language comes from the interaction between its features. The real problem is features that interact in unpleasant ways, which almost always results from a lack of foresight on the language designer's part. > In C++, RAII interacts with exceptions, which is the point but isn't exactly pleasant. The interaction between control effects (of which exceptions are a particular case) and substructural types (of which C++'s RAII is a very broken particular case) is certainly nontrivial [0], but this doesn't mean we should give up on either feature. Control effects make it easier to design and implement extensible programs. Substructural types allow you to safely manipulate ephemeral resources, such as file handles, database connections or GUI objects. [0] http://users.eecs.northwestern.edu/~jesse/pubs/substructural-control/CtlURAL-long.pdf http://users.eecs.northwestern.edu/~jesse/pubs/substructural...
- bjwbell 11y agoNice phrasing, unpleasant was the feeling I was going for. Sometimes I wonder about giving up.
- pron 11y ago> The interaction between control effects (of which exceptions are a particular case) and substructural types (of which C++'s RAII is a very broken particular case) is certainly nontrivial A nitpick, but what constitutes an effect is rather arbitrary. An effect in the PFP sense is not an operational definition (other than IO) but a linguistic one. This is why I think that handling errors well, handling mutation well and handling IO well are three completely different problems that are only accidentally bundled into one by PFP for no cognitive/empirical reason other than that the lambda calculus happens to be equally challenged by all three. There is a fourth effect, which is just as operational as IO (and thus a "truer" effect than errors or mutation) and is often the most interesting, yet it happens to be the one that baffles PFP/LC most: the passage of time. This is why there are usually two ways to sleep in PFP languages, one considered an effect, and the other is not (but happens to be much more operationally disruptive, and thus a stronger "effect").
- 11y ago
- catnaroek 11y ago> Complexity is not about additivity, it's about entanglement. This. And nothing reflects entanglement better than a formal semantics. English (or any other natural language) always lets you sweep it under the rug. The only objective measure of simplicity is the size of a formal semantics. I expand on this here: https://www.reddit.com/r/programming/comments/3sstis/for_better_or_for_worse/cx0cp0q https://www.reddit.com/r/programming/comments/3sstis/for_bet...
- deleted 11y ago[deleted]
- EdiX 11y ago> nothing reflects entanglement better than a formal semantics A formal semantics is just a way to translate from one formalism to another. It's rather obvious that choosing the target formalism determines how simple the language will appear, when you talk about "formal semantics" you should specify "which one": operational? denotational? axiomatic? Stricly speaking a compiler or an interpreter represents a formal semantics for a language: operational semanthics rules are often very very similar to the code of an AST interpreter, for example. One could interpreter your statement to mean that the smaller the compiler the simpler the language, which means that assembly language was the simplest language all along! For example, in your reddit post you claim that := is problematic, and indeed its semantics is tricky and often trips beginners (and even experienced!) programmers. However := semantics is not actually that complicated "define every variable that isn't defined inside the current scope, otherwise assign them" and the errors stem from the fact that people assume that the scope lookup for := is recursive, which would arguably result in a more complicated formal semantics.
- catnaroek 11y ago> A formal semantics is just a way to translate from one formalism to another. Of course, we need to reach a gentleman's agreement regarding which formalism is a good “foundation” for defining everything else. My personal preference would be to define all other formal systems in terms of rules of inference. > It's rather obvious that choosing the target formalism determines how simple the language will appear, when you talk about "formal semantics" you should specify "which one": operational? denotational? axiomatic? I am fine with any, as long as the same choice is made for all languages being compared. What ultimately interests me is proving a type safety theorem, that is, a precise sense in which “well typed programs don't go wrong”, so perhaps this makes a structural operational semantics more appropriate than the other choices. > Stricly speaking a compiler or an interpreter represents a formal semantics for a language: operational semanthics rules are often very very similar to the code of an AST interpreter, for example. > One could interpreter your statement to mean that the smaller the compiler the simpler the language, which means that assembly language was the simplest language all along! Sure, but the target languages used by most compilers are often themselves very complex. Which means a realistic compiler or interpreter most likely won't be a good benchmark for semantic simplicity.
- nulltype 11y agoIsn't that exactly what Rob Pike is saying with the vector space analogy?
- knucklesandwich 11y agoI think that's what he's appealing to, but I have a hard time reconciling that sentiment with many design characteristics of Go. Go's type system for instance... I don't think he fully grasps "what he's trying to solve" by having a static type system in golang, when the language has things like unsafe casting, null pointers, a lack of parametric polymorphism, etc. As a programmer tool, its hugely weakened by these design decisions... there are large classes of properties about code that are simply impossible (or are much more complicated) to encode using types in golang. And yet in their literature on some of these subjects, they make an appeal to simplicity [1]. I think there's a disconnect here between theory and practice. [1] https://golang.org/doc/faq#generics https://golang.org/doc/faq#generics