11 ms·
A Pythonista's Review of Haskell
- pjdorrell 7y agoIn Python type(type) is type evaluates to True. And that is not permitted in mathematical type theory that the Curry-Howard correspondence applies to.
- yingw787 7y agoOn lobste.rs somebody pointed out that undefined satisfies the Curry Howard correspondence for Haskell, and I was wondering whether the proper analogue for Python would be NoneType in that case. Comment here: https://lobste.rs/s/ovebeq/pythonista_s_review_haskell#c_gz09vc https://lobste.rs/s/ovebeq/pythonista_s_review_haskell#c_gz0...
- TheAsprngHacker 7y agoWhat Python and other dynamically typed languages call "types" aren't types at all in the theoretical sense. In type theory, types are properties ascribed to programs that describe their behavior and are part of a language's static semantics (AKA checked at compile time). Dynamically typed languages are really untyped (or from a certain perspective, "unityped," meaning that every expression has the same type: https://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages/ https://existentialtype.wordpress.com/2011/03/19/dynamic-lan...).
- yingw787 7y agoThis is a very interesting perspective! I’ve never thought about dynamic languages as a single typed static language. Thank you for sharing!
- ridiculous_fish 7y agoNote many dynamically typed languages have static types. Objective-C, Java, TypeScript, Dart, etc. The dynamic types bring new capabilities above and beyond what is possible with static types. (That is, the dynamic-or-static choice is a product type, not a sum type!)
- greydius 7y ago> The dynamic types bring new capabilities above and beyond what is possible with static types. Not really. "Dynamic type" just means an object that carries around a description of its structure (or more commonly, a pointer to a description). You can do the same thing in many languages with static types. (Including Haskell. GHC has special support for this pattern.)
- giornogiovanna 7y agoWhat special support does GHC have?
- nybble41 7y agoGHC can derive a Typeable type class instance for any data type which allows values of that type to be stored in a Dynamic wrapper. For soundness reasons the Typeable type class is special in that it can only be derived; you can't implement it manually. The runtime types of Dynamic values can be queried and compared and you can attempt to extract a specific known (concrete) type, which will succeed only if the runtime type matches. There are some limitations; for example, you need to know the exact type of the value you're extracting, not just a type class constraint, so you can't e.g. add two Dynamic values directly even if they hold the same type of value at runtime with a valid Num instance. Alternatively, you can just define a recursive sum type expressing all the possible runtime types of the dynamic expression. In Aeson for example there is a type which can encode any value which might be found in a JSON file, which is basically everything you can express in Javascript apart from function closures. This doesn't require any special support from GHC and works well in most statically-typed languages.
- correct_horse 7y agoI've heard this called "unit typed" (see the Wikipedia page of this name), I think unitype may be incorrect. i.e. Haskell includes the unit type ()
- lonelappde 7y agoHaskell's unit type is what other languages call "void", the type with exactly one value (not counting 'undefined', Haskell's dirty little not-secret). That's different from Unitype/Any, the type that contains all values. Bob Harper uses "unitype" which is a pretty strong argument for the name's validity
- nybble41 7y ago> not counting 'undefined', Haskell's dirty little not-secret There is no "undefined" value in pure Haskell. There are expressions that have no value, due to a runtime exception (error, undefined, division by zero, memory allocation failure) or non-termination (unbounded recursion). Either way, evaluating the expression does not produce a value for the remainder of the program to operate on—you can't write a (pure) program like `if isUndefined expr then x else y` since—assuming isUndefined is a pure function and doesn't ignore its argument—the whole program is undefined if expr is undefined.
- pdexter 7y agoAlthough it itself is deprecated, the TypeInType extension does exist. https://www.seas.upenn.edu/~sweirich/papers/fckinds.pdf https://www.seas.upenn.edu/~sweirich/papers/fckinds.pdf https://www.reddit.com/r/haskell/comments/4180k3/what_is_typeintype/ https://www.reddit.com/r/haskell/comments/4180k3/what_is_typ... https://www.reddit.com/r/haskell/comments/4180k3/what_is_typeintype/cz0onpy/ https://www.reddit.com/r/haskell/comments/4180k3/what_is_typ...
- matt-noonan 7y agoAnd the only reason the language pragma is deprecated is because TypeInType is now just how things are, by default, with no opt-out.
- Areading314 7y agoThis page made me permanently write off Haskell: https://wiki.haskell.org/Prime_numbers https://wiki.haskell.org/Prime_numbers It is so much work, so much effort to develop some very mediocre, very slow, and incomprehensible implementations of an undergraduate level algorithm. I ended up picking CL for the project I was working on (Project Euler) and never looked back.
- yingw787 7y agoYou are of course entitled to your own opinion, but I would say that’s unfairly harsh towards Haskell. GHC Haskell not only compiles down to a binary, it can also transpile to C—-, both of which can run performantly. I started off FP with lodash, then moved to Python and built in functions, and I’ve tasted the benefits of applying functional programming in production, and so that’s what makes learning Haskell interesting and worthwhile to me. I’m looking forward to bringing what I’ve learned from Haskell towards the rest of the code I write, even if it’s not in Haskell.
- Areading314 7y agoI write Python for a living, and it is an astoundingly productive language. What it gets right is the compromise between functional, object-oriented, and imperative styles. I too have been fascinated by the "pure functional" type languages, but they always fall short when it comes to "real world" projects.
- yingw787 7y agoI think it’s definitely hard for practitioners to adjust to FP, especially when under the pressure to ship. I think a great opportunity to look for ways to apply FP would be in small utilities or libraries. I’m planning my next project in Python but if I’m fortunate to ship that I’ll see if I can circle back to Haskell and go from beginner to intermediate by building a small library.
- Areading314 7y ago
- ngcc_hk 7y agoAny js-er review?
- yingw787 7y agoIf you mean Javascript to Erlang, probably not for the foreseeable future. I want to learn to ship product for a bit, and hopefully I can talk about that :)
- hopia 7y agoWhat kind of products are you building?
- yingw787 7y agoIt’s under wraps right now (mostly because I can’t look people who believed in me in the eye if I fail) but it’s a tiny CRM for software engineers. Feel free to subscribe to my mailing list on the blog for more information, or email me at me@yingw787.com :)
- mcaruso 7y agoI'm a JS dev that learned Haskell over the past few years. Anything in particular you're curious about? I guess if I were to mention one thing I really miss in Haskell it's having easy, generic dictionary types a la JSON. Give me some syntax sugar to write a dictionary like JSON objects, where the properties are ordered by insertion (unlike most HashMaps that are unordered or rely on some predefined Key ordering). Also I'd love some better record types.
- intrepidhero 7y ago"A functor instance lifts a function over some structure to affect only the values within. An applicative (a monoidal functor) leverages a structure and function together while lifting the function over structure. A monad (an applicative functor) creates new structure whilst in the process of lifting a function over existing structure." I think https://xkcd.com/483/ https://xkcd.com/483/ should apply to programming languages too. ;-) But seriously, can someone put this in English? Is it really more complicated than "apply a function to every object in a list"?
- yingw787 7y agoI actually wrote a monad tutorial last year: https://bytes.yingw787.com/posts/2019/12/06/monads/ https://bytes.yingw787.com/posts/2019/12/06/monads/ After reading about monads...honestly no, there is no way to simplify it. Without understanding the type system, it’s alien.
- TheAsprngHacker 7y agoBut "apply a function to every object in a list" isn't the definition that the author is getting at! Most programmers probably know "map" as being an operation takes a function and a list and returns a list of the results of the function applied to each element of the input list. However, the "map" operation can be generalized to generic types other than lists, where it has different behavior but shares certain properties. In functional programming, the idea of a functor gives a name to this pattern. The idea is that if you have some generic type `T a` covariant in `a`, there is a function `map : (a -> b) -> T a -> T b` that can "lift" every function `f : a -> b` into a function `map f : T a -> T b`. The lifting operation respects function composition and identity. However, what if the function has multiple arguments, e.g. `f : a -> (b -> c)`? Then, `map f : T a -> T (b -> c)` An applicative functor lets you convert that `T (b -> c)` into a `T b -> T c`. So, with applicative functors, you can individually map each parameter of a multi-parameter function. A monad is a functor that can be "flattened." It has an operation `join : T (T a) -> T a`, as well as an operation `return : a -> T a`. A monad can be seen as generalizing the idea of flattening a list, or generalizing async/await.
- 7y ago
- crimsonalucard 7y ago>Haskell has no null definition of type Char, at least according to this Stack Overflow answer. Python doesn't distinguish between Char and String types with single and double quotes, so empty char is empty string is an empty list. It seems weird to me that for all the typing wealth Haskell provides, this base case doesn't exist, though I personally don't know what it is. An empty string is equivalent to an empty list [] If you want a null use the maybe monad.
- inimino 7y agoAs anyone who has used C will recall, a char is just a small integer. And just the same in Haskell, a char is exactly one character. Once you understand that, this confusion makes about as much sense as expecting an integer to be "empty". I suspect this kind of question would be better asked on the Haskell IRC channel or similar rather than trawling stack overflow for insights. If someone has the relevant missing insight they can quickly point it out and save a lot of confusion.
- crimsonalucard 7y agoit does make sense. 1/0 = undefined aka null. The thing with haskell is that the concept of null is typed as part of a sum type meaning it will be handled with no runtime errors.
- pdexter 7y ago*** Exception: Prelude.head: empty list
- giornogiovanna 7y agoYeah, I don't know why Prelude.head doesn't return a Maybe. Anyway, the point stands. • In Java forgetting to check for null will crash your program. • In C, forgetting to check for NULL will lead to undefined behavior. • In Haskell, you either have to (a) check for Nothing, or (b) explicitly say "crash the program if this is Nothing" by using a function like fromJust or head, which internally does (a). This is always an improvement over C, and often an improvement over Java. Side note: I like Rust's convention of consistently naming these might-crash-your-program methods "unwrap" and "expect".
- unnouinceput 7y agoQuote: "I don't think you can do anything like this with Python or any other practitioner's language that I'm familiar with" So, then you're not familiar with C/C++, Java, C#, Pascal/Delphi, Ada - to say the least.
- rgoulter 7y agoWhat's the equivalent of -- zs' = [Just 2, Nothing, Just 4] zs' = (fmap . fmap) (+1) [Just 1, Nothing, Just 3] in C?
- FeepingCreature 7y agoAt least on the D side: Nullable!int[] zs = [2.nullable, Nullable!int(), 4.nullable]; // no partial application, and // separate template parameters and runtime parameters // so (fmap . fmap) (+1) is one function Nullable!int[] zs2 = map!(apply!(i => i + 1))(zs).array; The C version probably has a lot more function pointers, and may use nonstandard compiler functions like closures. But in principle you could do the same thing.
- sweeneyrod 7y agoThat's not the same, it's just composing the maps for arrays and nullables. The whole point of fmap is that you can write (fmap . fmap) (+1) [Just 1, Nothing, Just 3] but also (fmap . fmap) (+1) Just [1, 3], rather than using (list_map . option_map) (+1) and (option_map . list_map) (+1) respectively. The usefulness of this is arguable. In my biased opinion as someone who favours OCaml (which lacks the typeclasses that allow this) over Haskell, I think it's minimal. While it's definitely useful to recognise that lists, options and promises are examples of the same pattern, I don't this generalisation has many benefits in code (at least in MLs where you don't need this pattern just to print things).
- FeepingCreature 7y ago> The usefulness of this is arguable. And I have argued it on D, and we very deliberately did not go with the range semantics. Optional types are not ranges unless you're compulsively hunting for forced commonality. Might as well treat any value as a range of 1.
- nurettin 7y ago> IMHO, the solution to high cognitive load, given a high enough reward, is just persistent, methodical execution towards learning. It will never converge towards being easy to handle. The load doesn't come from being inexperienced, it comes from keeping the big picture in your head (all your types and their interactions) instead of iterative thinking.
- whateveracct 7y agothis isn't true - at least not in my years of professional experience I barely keep anything in my head most of the time when programming in haskell. It's kind of related to the "emphasis on structure" point from the article. All of a type's "interactions" are just functions. There aren't any second-order things to consider due to referential transparency.
- hopia 7y agoI agree, type signatures & inference and referential transparency offload the cognitive load from your head to the compiler/IDE. Functions that actually work on the data structures are generally guaranteed to be able to work on the said data. This way you don't need to constantly be mindful of what the data looks like, the compiler does that for you.
- anchpop 7y agoThis exactly. Although the effect is much greater if you turn on some complier warnings (like -fwarn-incomplete-patterns). The emphasis on coming up with types that contain the guarantees you need makes it so once it compiles, you don't have to remember what guarantees you need to have. Although doing that is not nearly as convenient in Haskell as it could be (types often don't compose well, and you need to add strictness annotations here and there to prevent `undefined`), it is actually possible which is a big step up from languages where you can't even try like C or Python.
- sjakobi 7y agoFYI OP: I posted your review on r/haskell where it got several comments: https://www.reddit.com/r/haskell/comments/ewjnf4/a_pythonistas_review_of_haskell/ https://www.reddit.com/r/haskell/comments/ewjnf4/a_pythonist...
- yingw787 7y agoThank you very much for sharing it there! The more the merrier :)
- hopia 7y agoMaking it to the end of an almost 2000 page Haskell book in only 3 months is quite the feat. While quite confusing to many, myself included, I would've loved to hear more about the types vs terms distinction, and overall about what's possible with type level programming. That's something fairly unique among programming languages after all. I'd certainly be interested in reading more of the author's thoughts if he continues down the path of using Haskell to solve problems.
- bojo 7y agoI would highly suggest https://thinkingwithtypes.com/ https://thinkingwithtypes.com/ - it's not a 2000 page monster, and specifically addresses what you are interested in.
- hopia 7y agoI've been reading that book occasionally for months but honestly, many parts are somewhat too advanced for me for now. I've sort of backed off from type level programming for now to maintain productivity with building actual things. Still, I'd like to read about it from someone coming from imperative languages.
- yingw787 7y agoThank you! I’m on sabbatical so I was reading full time. I believe John (the guy who wrote the set of notes I credited) started writing them two years ago, and did two passes over the book. I also skipped a bunch of exercises nearer the end (parser combinators I need to review). Don’t feel bad if you take your time! Python is still my strongest language, and I’m planning on shipping product for a bit after this, so I will be using that for the next few months. I’ll do my best to circle back to Haskell, I love the community and see its potential in what I want to do. Subscribe to my email list if you want to stay updated!
- zzo38computer 7y agoThey mention indentation, but you don't have to use their indentation and can use braces and semicolons instead if you prefer, which is what I prefer, too. So, I think the indentation isn't so much of the problem.