22 ms·
Examples of Incorrect Abstractions in Other Languages
- D13Fd 6y agoIt's threads like this that remind me how little I know...
- mantap 6y agoA lot of Haskell jargon is just simple everyday concepts dressed up with complicated neologisms.
- lgas 6y agoSure, if you consider precise mathematical terminology to be complicated neologisms.
- _bxg1 6y agoYou're not wrong that the terminology is needlessly confusing, but that also doesn't mean the ideas themselves are obvious or aren't powerful. The same happens in many academic spheres; good ideas are created, and then they're also dressed up in obscure language to make the creators feel like the concepts are as hard to understand as they were to invent.
- antonvs 6y agoCould you give an example?
- pas 6y agoMonad. It's just a very generic container. You can put things into it (unit/pure/return/bind) and you can do things within it (flatmap). Sometimes it's an effect or control flow container (Try, Future, Result, Either, Maybe, Option), sometimes it's just a List. And then there's cata (catamorphism), which is the generic reduce/fold/accumulate/aggregate. And then there's bifunctor, which is the general Either, Result, or List[(A,B)] thingie. These concepts are very useful, but usually typed FP code nowadays are full of compromises in the name of typing. Typed FP code is constrained by what the type system allows. This eventually boils down to everything becoming for-comprehensions / do-notations. Which is fine after you get the hang of it. But it's very easy to run into real world problems that require "hacks" or inelegant solutions, or forces the correct solution, which is very time consuming.
- antonvs 6y agoMonad has a specific set of mathematical rules, though, which have important properties which make them well-suited for some very specific tasks, including defining the semantics of programming languages. I'm not aware of any "simple everyday concept", or anything in any mainstream programming language, that comes close to being equivalent to what monads are. You can find examples of specific monads being used (and misused) in many ordinary programs, because of their deep connection to programming language design, but they're never explicitly identified as such, partly because most languages can't conveniently make use of an abstraction like that. Similarly, there's more to catamorphisms than what you describe. What you described is essentially what the generic mathematical concept of a catamorphism looks like in a specific context, but that's like saying that the term "vehicle" is just the simple everyday concept of a bicycle dressed up with a complicated neologism. Something similar goes for bifunctor. `Either` is an example of a bifunctor, but it's also an example of a functor. Conflating the generic concept with specific instances will mislead you. The point about this terminology, and the concepts it represents, is that it classifies these abstractions with precise rules, allowing the commonality across many different abstractions to be identified and exploited in well-defined, rigorous ways. You don't find that in mainstream programming languages or the literature about them. The closest you'll come is things like object-oriented collection class hierarchies - Collection / List / Set / OrderedSet etc. That kind of classification is analogous to the sort of terms we're discussing, but is nowhere near as well-factored or mathematically rigorous. > This eventually boils down to everything becoming for-comprehensions / do-notations. What language are you thinking of? Sounds like Scala maybe. Unfortunately I think Scala was a mistake from the point of view of introducing functional programming to a wider audience. The object/functional mixture results in a kind of worst of both worlds that doesn't show off the benefits of either to best effect. > usually typed FP code nowadays are full of compromises in the name of typing. > But it's very easy to run into real world problems that require "hacks" or inelegant solutions, or forces the correct solution, which is very time consuming. Perhaps you've had this experience from experimenting with functional languages, but it's not my experience from actually using languages like Haskell and OCaml in real projects for many years. I'm also very experienced with many mainstream languages - Java/Javascript/C/C++/Python etc. - and I find functional languages to be models of elegance by comparison. Statically debugging code by designing the types well ("make illegal states unrepresentable") is a huge timesaver when it comes to avoiding bugs and making significant changes to large codebases easier. There's a lot of research on extending the power of type systems, which it seems like you may be thinking of, but for most ordinary purposes, you don't need to worry about that.
- noelwelsh 6y agoMy favourite example, because I've been working with it recently, is the nunjucks templating language. It has three or four different ways of almost-but-not-quite implementing functions. It has dynamic scoping (hello, 1960s!) But my favourite feature is its recursive for-loops. These are truly majestic. I would not believe someone could implement a language so broken if I had not witnessed it.
- bedobi 6y agoNull and exceptions. (in Haskell, Maybe and Try/Either)
- dmix 6y agoNothing beats pattern matching on Maybe for exception or null handling, or variations of such (Rust's Option). It's just so clean syntactically.
- mirekrusin 6y agoI’d argue that nullable type with refinements as in flow for example beats Maybe most of the time. You can’t make it simpler (one letter ? overhead) without special unwrapping constructs (refinements) just normal code with same guarantees.
- lmm 6y agoRefinement typing is too complicated IME. Haskell guarantees that you can always pull out an expression into a function without changing anything, and vice versa, whereas with refinement types you can extract a function and find your code no longer compiles.
- js8 6y agoYes, null is, as it turns out, an incorrect abstraction. It's like saying that all your types are Maybe a, and you cannot get create a type that is not (with few exceptions of base types, which confuses it even more). I would also add Enums to the list, sum types are superior in all respects.
- christophilus 6y ago> And yet, for a long time, it lacked a function analogous to <>, which is strange when considered from a "folds are Applicative" perspective. Man. I enjoyed hacking around with Haskell back in the day, but I found it to be a write only language with forms like <> making me stop and stare too often. Also, after maybe 15 years of working in statically typed languages, I also decided that ultimately, I prefer dynamically typed languages. Still have yet to use my favorite (Clojure) in production, though.
- bedobi 6y agoHave a look at Kotlin with the Arrow library, it's a great compromise
- outworlder 6y ago> Also, after maybe 15 years of working in statically typed languages, I also decided that ultimately, I prefer dynamically typed languages. Still have yet to use my favorite (Clojure) in production, though. I like to have types when I need types. If I want to write a numeric value in a json output I'm creating that will be consumed by some other system, I want that thing to represent exactly that and spit an error if I ever try to assign something else, such as a string. But more than that, it should be easy to define my own types. I need to be able to say, "this number is not just any number, this is actually a temperature measurement in Celsius" or "this is talking about screen coordinates, not world coordinates". And then only be able to send that to the correct functions – or treat as a normal number if I'm doing basic math. But most of the time, most people don't care about types. We want to say "do this operation across all elements of this array". If I trust whatever lower level function that added the properly typed elements in the first place, now I can focus on manipulating them. Glue code is very amenable to dynamic types. Haskell has a very rich type system (maybe too rich?), but it also allows you to say that function receives an A and returns [A]. What's A? If you don't care, you don't care. That's beautiful. And the compiler can even inference some of it for you. Now, if the only thing I have is a poor type system(like Java), then just give me primitives and a struct.
- JCoder58 6y ago
- lhorie 6y agoFun trivia, the promise absorption thing was dismissed when it was brought up, with the argument that the "ship had already sailed on that" and that the arguments for supporting promises of promises were too academic (in the detractors' words, it was "fantasy land"). Which then resulted in the creation of a specification called fantasy-land[1] as a form of mockery towards the "incorrect abstractions are fine" attitude. The goal of this spec is to standardize on functional algebra nomenclature in JS. [1] https://github.com/fantasyland/fantasy-land https://github.com/fantasyland/fantasy-land
- k__ 6y agolol, I knew fantasy-land, but I didn't know the story, thanks! I have to admit, when I read stuff by hardcore FP proponents like in that Reddit thread, I also feel the urge to call it fantasy land.
- andolanra 6y agoSome of these are arguably "incorrect abstractions", but others are just design decisions that are being treated as "incorrect" because they don't follow Haskell's design sensibilities. A perfect example is the complaint about sum in Python: >>> sum(["a", "b"], start="") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: sum() can't sum strings [use ''.join(seq) instead] I don't think this is incorrect at all! There's a very good reason to use join instead of sum for adding strings together: join is a lot more efficient than simply adding strings together, because it will precompute the amount of space needed for the resulting string at once and then put all the strings into the buffer at once, whereas using sum in the naïve way suggested would do a different append for each element. These are just different tools for different purposes! Could you design a sum that automatically looked at its argument type and deferred to join for strings? Sure! And that'd be a valid design decision as well. But the specific choice Python made here is motivated and defensible: it's only "incorrect" if your personal definition of "correct" is "built with the same algebraic sensibility that Haskell's designers had," but at that point, all other languages are "incorrect" because they're not Haskell!
- draw_down 6y ago> at that point, all other languages are "incorrect" because they're not Haskell! Right... but, hard not to see that coming as soon as you see the title of the link. In a design context, what could "incorrect" mean but "I don't agree with it"? Ditto "simple".
- mokus 6y agoI agree with your overall point, but regarding your “could you design....” rhetorical: in that example, isn’t it already inspecting the type of the list elements and then doing a specific different thing (printing that message) if it happens to be a list of strings? Why not just go one step further and just do the thing instead of nag the user to do the thing?
- karatestomp 6y agoSum and join mean different things. If sum didn't fail on chars I'd expect it instead to sum their binary values, which is a thing one occasionally actually does want to do, not to join/concatenate them. If it can't do that (or you have strings, as in this case, for which it makes less sense—the most sensible thing there would probably be to treat it as a byte array and do something with that, though any conceivable use of that probably breaks down with UTFs 8 or 16 instead of ascii) I'd want it to fail.
- saagarjha 6y agoGood link: https://old.reddit.com/r/haskell/comments/glz389/examples_of_incorrect_abstractions_in_other/ https://old.reddit.com/r/haskell/comments/glz389/examples_of...
- barbarbar 6y agoThis reminds me stdout's fine tune: My language is better than your language. It captures a lot of what this thread is about.
- legerdemain 6y agoThis is a thread from r/haskell. I think a fair summary of the contents is "things Haskell people dislike about other languages."
- smcl 6y agoTo me it looks like someone who wants to compile a list of arguments they can refer to when they are having an argument about programming languages
- bjoli 6y agoI never understood the complaint about generators. Languages can have both light-weight laziness (like generators) and more heavy abstractions (like scheme's srfi-41). The overhead of generators in, say, JavaScript is minimal. If you want odd lazy lists, generators is a great thing to use to build it. Generators and streams (as in scheme's SRFI 41) are not mutually exclusive.
- js8 6y agoI think that classes (in the school of OOP coming from C++ and Java) are incorrect abstractions. Here incorrect is in the eye of the beholder, but I would argue that tying the three fundamental principles of OOP (encapsulation, inheritance, polymorphism) into one abstraction leads to more problems than it solves. In particular in Haskell, these things are separated into modules (encapsulation mechanism), abstract data types (inheritance mechanism) and type classes (polymorphism mechanism). But other modern languages have this separation as well, and it is cleaner. Consider for example a common Java exam question, should you use interface or abstract class? This is a completely artificial problem that doesn't occur in languages that separate these abstractions orthogonally. Also another common exam question, access control (private, public, protected) is more complicated due to lack of the separation of encapsulation and inheritance principles. Another example is Java is arguably much less readable than it could be because organizing all the code as classes makes code more difficult to follow (especially without IDE it's insanity of directories) - you don't know which classes are just utility types or simple functions and which actually contain the meat of the code. A properly organized module is much more pleasant to work with.
- legerdemain 6y agoIn Java, it is perfectly acceptable to have either a file containing the primary public class and any number of private utility and implementation classes, or just have a non-instantiatable enclosing class that works as a module and a bunch of interfaces, classes, and enums within it. Consumers then can then refer to them either by their Class.SubClass name or the shorter SubClass name.
- js8 6y agoOK, thanks! I didn't realize it is possible, I haven't seen code that uses this technique.
- watwut 6y agoTypically nested classes in Java make it harder to follow what each class does, where it ends and starts. The files then tend to become too big and at worst end up cross referencing each others privates too much. At least, that is my experience with code that uses nested classes all the time and - I am slowly refactoring it to split classes into separate files.
- toolslive 6y agoThis classic contains a lot of them: https://www.destroyallsoftware.com/talks/wat https://www.destroyallsoftware.com/talks/wat
- donatj 6y agoI was thinking just yesterday while casting an int32 to a uint64 that theres no guarantee the int32 wasn’t negative beyond my knowledge that the code was only ever incrementing it, and how most languages (that I have worked with at least) allow dangerous casting without enforcing any sort of error handling on the given operations. I think this would fall into this category.
- japanuspus 6y agoYou should definitely have a go at rust where this conversion would have to be done explicitly via `try_from` [0] which may fail. [0]: https://doc.rust-lang.org/beta/std/convert/trait.TryFrom.html https://doc.rust-lang.org/beta/std/convert/trait.TryFrom.htm...
- millstone 6y agoIf Unicode were a monad, Haskell would have gotten String right.
- wyattpeak 6y agoI occasionally find myself interested in learning Haskell, wondering why I haven't done so yet. So I go out and find an introductory tutorial, which is without fail written with exactly the sort of smug self-satisfaction that this thread is written. And I immediately lose interest. Maybe the Haskell community is genuinely peopled by the most brilliant minds ever to grace the planet, and the rest of us are out here farming shit. I'll never know, because I'd rather struggle for a lifetime through poorly-implemented languages than have to turn every time I have a problem to a community completely unable to process the idea that they might be wrong.
- mrkeen 6y agoIt sure is easier to bash people than learn a new language!
- wyattpeak 6y agoWhile you're not necessary unfair in your jab, I have trouble mustering any sense of guilt when I'm commenting on a thread purporting that every alternative choice in other languages is in fact an error on the part of its creators.
- tome 6y ago> I'm commenting on a thread purporting that every alternative choice in other languages is in fact an error on the part of its creators. That's because the thread is called "Examples of Incorrect Abstractions in Other Languages". You will exactly read about poor choices on the part of other languages' creators. If you want to read about poor choices on the part of Haskell's creators then read threads discussing those. You'll find the Haskell community can provide plenty of examples for discussion there too. Haskell: The Bad Parts https://www.reddit.com/r/haskell/comments/4sihcv/haskell_the_bad_parts/ https://www.reddit.com/r/haskell/comments/4sihcv/haskell_the... What Template Haskell gets wrong and Racket gets right https://www.reddit.com/r/haskell/comments/4tfzah/what_template_haskell_gets_wrong_and_racket_gets/ https://www.reddit.com/r/haskell/comments/4tfzah/what_templa... If you had the ultimate power and could change any single thing in Haskell language or Haskell ecosystem/infrastructure, what would you change? https://www.reddit.com/r/haskell/comments/9fefoe/if_you_had_the_ultimate_power_and_could_change/ https://www.reddit.com/r/haskell/comments/9fefoe/if_you_had_... examples of bad warnings/errors? https://www.reddit.com/r/haskell/comments/4yxog6/examples_of_bad_warningserrors/ https://www.reddit.com/r/haskell/comments/4yxog6/examples_of... What's wrong with GHC Haskell's current constraint system? https://www.reddit.com/r/haskell/comments/117r1p/whats_wrong_with_ghc_haskells_current_constraint/ https://www.reddit.com/r/haskell/comments/117r1p/whats_wrong... Could you give me an example where Haskell is not good ? https://www.reddit.com/r/haskell/comments/3b94w0/could_you_give_me_an_example_where_haskell_is_not/ https://www.reddit.com/r/haskell/comments/3b94w0/could_you_g...
- crimsonalucard1 6y agoDon't forget about Go: func Open(name string) (file *File, err error) Your basic Optional/Maybe monad would have worked better here. There is a lot of evidence supporting the fact that Rob Pike and company when inventing Go were not aware of Sum types indicating that this special Tuple return type (that can never be reused anywhere and must be pattern matched immediately after it returns) was created because of lack of knowledge and not as a design choice. See this thread as evidence: https://news.ycombinator.com/item?id=6821389 https://news.ycombinator.com/item?id=6821389
- nsajko 6y agoWhere did you learn (or, more generally, where do people learn) about those Sum types? The Wikipedia page for "Sum type" redirects to "Tagged union", implying those are synonyms; do you agree with that? Sadly, that Wikipedia page only seems to refer to pages specific to some programming languages. Could you maybe recommend an introductory book about type theory, preferably with an orientation to how it can be applied to language design? EDIT: or maybe a programming language theory book? To be honest, I am mainly interested in imperative languages, basically how could a better Go have been made.
- crimsonalucard1 6y agoLanguages with Algebraic Data Types will help you get practical knowledge on how to use these types. Basically types share an isomorphism with algebra allowing certain types to share properties with algebra, hence the name sum types and product types. Bool has a cardinality of two. True and False. A Product type (essentially a tuple) of two bools represents a bool and another bool and has a cardinality of 4. (True, True), (True, False), (False, False), (False, True). The cardinality of a product type is essentially the product 2 * 2 of the cardinality of both individual types. Product types represent the concept of AND: a bool AND another bool. The product type in itself is a type evaluated as the product of two (or more) types. In most languages this concept is represented as a struct or a tuple. To remove confusion for sum types let us create a type with a cardinality of 4. FuzzyBool. It has 4 instances: VeryTrue, SortaTrue, SortaFalse, VeryFalse. A sum type represents the concept of OR. Let's see a sum type of a Bool OR a FuzzyBool (Bool + FuzzyBool). The new type can either be one of: (True | False | SortaTrue | SortaFalse | VeryTrue | VeryFalse). The cardinality is 4 + 2. Hence the term sum type. Languages are typically asymmetric in types due to the lack of the explicitly offering the user the ability to combine types to form a sum type. The Golang return value for functions is actually the opposite of a sum type. It is a product type. Rob Pike was likely unaware that you can create a type that is either (File* or Error) and instead utilized ((File* | null) && (Error | null)) to attempt to keep error handling within the type system. It results in IO functions having a return type with Cardinality of greater than 4: (File, nil), (File, err), (nil, nil), (nil, err) and they just expect you to ignore (File*, err) and (nil, nil) and assume it will never occur even though the type system does not restrict it from occurring. If you want to learn this stuff imperatively I would recommend Rust, however you could miss the big picture with Rust due to the borrow checker and the option to do typical C-style programming that avoids utilizing the ADT's to the full extent of their power. I would recommend diving into something like Haskell and getting to know algebraic data types and exhaustive pattern matching. You can avoid fully understanding Monads if all you want to understand is Sum types. Haskell will force you to utilize sum types very early on via exhaustive pattern matching.