[HN Gopher] Pedagogical Downsides of Haskell
___________________________________________________________________
Pedagogical Downsides of Haskell
Author : scscsc
Score : 63 points
Date : 2023-04-12 05:32 UTC (1 days ago)
(HTM) web link (ciobaca.substack.com)
(TXT) w3m dump (ciobaca.substack.com)
| agentultra wrote:
| I wonder if there could be a (or already is) a "teaching" Prelude
| designed for this purpose.
|
| One of the reasons the standard Prelude includes partial
| functions and specialize versions of `map` and `filter` is to
| support the pedagogical use-case (as far as I understand the
| situation). Most production applications will use a custom
| Prelude of some kind in order to prevent programmers from using
| foot-guns like `head` or make things more general in the case of
| `map` and `filter`.
|
| Turns out using linked-lists for everything isn't the best idea
| but a lot of Haskell applications will use them because it's in
| Prelude.
|
| Bit of a balancing act supporting both use cases.
| jerf wrote:
| I don't know if there is one already, because the Haskell
| community generally heads in the other direction with its
| alternate Preludes.
|
| But the effort to fix up the fixable issues mentioned in the
| post is about the same as writing the post was. Getting it
| distributed to the students may be a bit harder, depending on
| the local setup.
|
| But it's definitely fixable with Haskell as it is today.
|
| Linked lists are particularly tricky in Haskell, because as a
| data structure manifested in memory, they really stink. But as
| a lazy data structure traversed exactly once and thus just
| serving as a mechanism for providing "the next thunk", they're
| fine. Haskell and its laziness completely conflates the two of
| these, so it ends up being easy to think you have one and end
| up with the other.
| agentultra wrote:
| Definitely. Linked lists are great for pedagogy and useful in
| many applications. I think it's a bit of a sign that the
| struggle between pedagogy and practice can lead to suboptimal
| outcomes for both parties.
| 0zemp1c wrote:
| [flagged]
| tome wrote:
| Can you give an example of a Haskell way that is not Right?
| consilient wrote:
| Typeclasses. They're a huge improvement over OO-style
| overloading, but using them as your default way of talking
| about structures is a Faustian bargain:
|
| - Implementing two instances of one member each is much more
| work than one instance with two members, which incentivizes
| overly coarse typeclass hierarchies: hence Num and friends,
| which are a disaster.
|
| - They don't provide a good way to talk about one structure
| uniquely determining another (e.g. a given semigroup can be
| made a monoid in at most one way), which leads to boilerplate
| overly pessimistic constraint resolution.
|
| - The way constraint resolution works forces them to take
| _type constructors_ , not types - which makes aliases, type
| families, etc. second class citizens.
| 0zemp1c wrote:
| [flagged]
| TylerE wrote:
| Lazy by default, to name just one. Ocaml's immediate by
| default but opt-in lazy is so much better.
| tome wrote:
| Yup, will definitely accept that one.
| FpUser wrote:
| I generally trying to avoid single paradigm languages that are
| trying to show me the one and only "true" way. I see no business
| benefits coming of of their use.
| mrkeen wrote:
| Brilliant write up.
|
| > There is also a school of thought that you should start Haskell
| by teaching the IO monad first, but I am not convinced: in my
| experience, if someone gets exposed to IO early on, they will
| contaminate all their functions with IO. They will essentially
| end up writing Java in Haskell.
|
| I don't think this is such a bad starting place. Crawling before
| walking. Purifying an (unnecessarily-) IO function into an
| ordinary function is a good exercise.
|
| Trying to enforce non-IO from the start would be like enforcing
| 'no new keyword & factories only' in another language.
| kirbyfan64sos wrote:
| I feel like the hard part is that, if you dive in early on with
| imperative-style code, it's really easy to try and do
| everything else the imperative "style" too...until you can't,
| or you run into some weird behavior stemming from how IO works,
| at which point you just end up super confused.
|
| Starting _without_ IO makes sure that you actually start to
| "get" how the language functions, so that once you jump into
| IO, the weird parts and how to mix it in with the logic written
| elsewhere makes a lot more sense.
| danidiaz wrote:
| > Purifying an (unnecessarily-) IO function into an ordinary
| function is a good exercise.
|
| Agree! And I would add that you can "purify" a monadic function
| without having to rewrite it in non-monadic style. You can make
| it polymorphic over all monads and relegate the "impurity" to
| monadic functions that you pass as arguments/dependencies. A
| trivial example: twice :: IO () twice =
| do putStrLn "foo" putStrLn "foo"
| twice' :: forall m. m () -> m () twice' action = do
| action action
|
| This is not that different to having a Spring bean that doesn't
| perform any effect directly--say, a direct invocation to
| "Instant.now()"--but instead receives a "Clock" object through
| dependency injection.
|
| Haskell lets you express the idea of "program logic that only
| has effects through its dependencies" by being polymorphic over
| all monads.
| kqr wrote:
| I agree. Haskell is a really good imperative language if that's
| what you want to use it for.
|
| And allowing beginners to write actual meaningful programs is a
| huge pedagogical benefit.
| kitd wrote:
| One of the reasons I liked the Haskell Wikibook [1] when trying
| to learn Haskell was that it didn't concern the reader with the
| IO monad until much later. It just presented 2 forms of using
| the language, a) normal functional style, b) an "imperative"
| "do" style, and then showed how they could be used together and
| when.
|
| That was enough to do most basic tasks and only later was it
| explained why they can't be mixed directly.
|
| [1] https://en.wikibooks.org/wiki/Haskell
| tome wrote:
| I find the go pattern absurd. Which of these is easier to read:
| foldr k z = go where go [] = z
| go (y:ys) = y `k` go ys
|
| or foldr k z = foldr_k_z where
| foldr_k_z [] = z foldr_k_z (y:ys) = y `k` foldr_k_z
| ys
| ghusbands wrote:
| I think I prefer this: foldr _ z [] = z
| foldr k z (x:xs) = k x $ foldr k z xs
| tome wrote:
| I suspect we all prefer that, but the point of abstracting
| out a closure that captures k and z is for performance.
| chowells wrote:
| Eh, the performance isn't from abstracting out a closure.
| It's from making the definition non-recursive so that it
| can be inlined. Then the compiler can see and inline the k
| and z parameters into the "go" block to eliminate indirect
| references. It's really all about inlining.
| tome wrote:
| If it was _just_ about making it non recursive so it
| could be inlined then the following would be sufficient:
| foldr k z = foldr' k z where foldr'
| k z [] = z foldr' k z (y:ys) = y `k` foldr' k
| z ys
|
| That's obviously not sufficient, so it must have
| something to do with the nature of the closure. In this
| case I presume that it's because the closure captures k
| and z, although if you have any evidence to the contrary
| that would be interesting to see.
| chowells wrote:
| That's a reasonable question. It comes down to being
| transparent with the compiler. Not redefining k and z at
| every step is what allows their values to be inlined. You
| could make an argument about a sufficiently advanced
| compiler and partial evaluation, but the fact is that
| partial evaluation is far too slow to rely on for things
| you could just make explicit in the code instead. When
| the definition closes over the names, they trivially
| refer back to the same thing every time. So when the
| definition of go is in the same scope as what k and z
| refer to (which is usually the case after inlining
| foldr), k and z can be inlined into go.
|
| When this happens, note that it's actually no longer
| constructing a closure at runtime. It has essentially
| closed over the values at compile time, using some very
| trivial transformations. If you use a definition that is
| too complex for those trivial transformations, you're
| getting in the way of the compiler doing its job. I
| always prefer to write my code with sympathy for the
| compiler. The less magic it needs to do, the better it
| does its job.
| abecedarius wrote:
| How about 'folding'? I've settled on that kind of name for
| looping/recursing helper functions.
|
| Scheme has a bit of syntactic sugar called "named let" which
| makes this internal-helper pattern more concise/direct.
| gpderetta wrote:
| The first.
| tome wrote:
| Do you have insight you can share into why you find it that
| way?
| chowells wrote:
| I do. The name foldr_k_z doesn't say what the function is
| doing. It's just syntactic punning on a function call with
| two additional arguments. That's actually negative for
| comprehensibility. Names should be semantic, not syntactic.
| And that name doesn't say a thing about its meaning. The
| most it tells you is that it's related to foldr and its k
| and z parameters. But the details? Well you have to look
| for those. When you look at the definition, you discover
| that the it's the foldr worker that closes over k and z.
| You could name it foldrWorkerThatClosesOverKandZ, I
| suppose. But does that name contain any information that
| isn't present in the context? Does it help you actually
| understand anything?
|
| I'd argue "of course not". You already know that it's the
| foldr worker because it's a local recursive definition
| inside foldr. And you already know it closes over k and z
| because it uses them without defining them locally. Nothing
| in that name provides additional semantic value.
|
| You could still use it anyway, on the argument that a
| little redundancy can help aid reading. But the more
| Haskell code you read and write, the less that redundancy
| helps you with anything. On the other hand, the
| proliferation of names that contain almost no semantic
| content starts to drag on you. And so an idiom was
| developed for naming recursive workers that do the core job
| of what the parent's name promises: just name it "go".
| Nothing to think about. It's reduced down to a level that
| communicates exactly that it's not clever. It's just doing
| the thing it has to do. And it's standardized. If you see
| it, you know exactly what it's doing. There's no need to
| waste time mapping a new name into your existing set of
| well-known patterns.
|
| So... As to the original argument's point? I think it
| probably is awkward for pedagogy. But it's absolutely
| better for actively using the language.
| yakshaving_jgt wrote:
| I think Elm is second to none as a tool for learning FP.
|
| It compiles quickly, the guidance offered in error messages are
| best in class, it's small, and the mental model is consistent.
|
| In fact I think it's far easier to learn Elm (and also perhaps
| web UI development wouldn't be such a shitshow if programmers
| earlier in their career used Elm to build their mental model)
| than it is to learn:
|
| - React
|
| - Redux
|
| - Immutable.js
|
| - Lodash/Ramda
|
| - ES${CURRENT_YEAR}
|
| - Webpack/Parcel/Grunt/Groan/Whatever
|
| - etc...
|
| I've seen so many early programmers go through some React course
| thinking they've learned FP, and yet struggle to solve basic
| problems by applying functions to values.
| javajosh wrote:
| The pedagogical downside of Haskell is that it ignores the
| physical reality of the machine. Physically, a computer is
| imperative, has mutating state, and is filled with all kinds of
| possible race conditions. Even after you apply the operating
| system, allowing processes to live together (and giving you space
| to define new ones), very few constraints are placed on your
| program and process space.
|
| Instead of building on this reality, Haskell asserts that the
| starting point is not physical reality, but rather a mathematical
| formalism called "The Lambda Calculus", the physical machine is
| looked at with disdain and pity, its limitations to be worked
| around to provide the one true abstraction. This is the original
| sin of Haskell, because it is an attitude that isn't driven by a
| need to make a thing, but aesthetics and a peculiar intellectual
| dogma around building that ultimately becomes a stumbling block.
|
| In my view, you _have_ to respect the machine. Abstractions can
| be beautiful, but they are ephemeral, changeable, unreal. The
| danger is that these illusions become a siren song to makers who
| are always looking for better tools, and to these makers the
| abstractions become realer than the machine. Haskell 's power
| users famously don't actually make anything with it (modulo
| pandoc and jekyll), and my guess is because either they find that
| 90% of real-world things you want to do are "ugly" from Haskell's
| point of view, and so are left as distasteful "exercises for the
| reader", or they get so distracted by the beauty of their tools
| they never finish.
|
| In any event, Haskell is a road less traveled for good reason.
| yakshaving_jgt wrote:
| > Haskell's power users famously don't actually make anything
| with it
|
| This is a lie that you're perpetuating.
|
| Myself and many of my friends, colleagues, and associates make
| a living writing Haskell.
| tikhonj wrote:
| In a bunch of key ways Haskell is _closer_ to the machine than
| modern languages like JavaScript: it doesn 't depend on a
| complicated JIT system at runtime, it lets you explicitly
| control details like boxing and unboxing, it exposes various
| low-level C and machine types (fixed-size ints/etc), it has
| primops for SIMD...
|
| You can write relatively low-level Haskell a lot more easily
| than you can write low-level JavaScript. You just don't _have_
| to.
| kqr wrote:
| I find so many things about this line of reasoning wrong that I
| don't know where to start. So let's just pick one thing:
| Haskell does not ignore the physical reality of the machine.
| It's one of few languages that explicitly recognise it.
|
| There are more facilities in Haskell to deal with this reality
| than in almost any other language you can think of.
| javajosh wrote:
| Well, in my defense I did offer a "line of reasoning" and not
| just a flat contradiction with no support.
|
| Also, I'm sorry for any discomfort. To use an analogy, if
| your friend starts dating a girl that you know is bad for
| him, you can't just tell him that. You'll get punched.
| Especially early on when he's totally in love. It doesn't
| matter if you're right or wrong about her, there's no
| argument that is going to win against love, and to say
| anything ill of her is only going to cause pain and harm your
| relationship with your friend. And love is love, this applies
| to a person or a software tool.
|
| I'm sorry for the discomfort, but I'm telling the truth as I
| see it and am not trying to hurt you. But Haskell, I think
| she's bad for you.
| AnimalMuppet wrote:
| Do you think Haskell recognizes the physical reality of the
| machine _more_ than C does? If so, how specifically does it
| do so?
| kqr wrote:
| Technically, within the language, yes. C itself simply
| delegates a lot of logic to the "physical machine"[1] by
| leaving it unspecified or implementation-defined. In
| contrast, Haskell actually tries to model these differences
| within the type system and standard libraries, with IORefs,
| STMs, and whathaveyou.
|
| (In fact, I just checked the IORef documentation and it
| actually references the x86/64 architecture manual to
| explain some of the behaviour that can be expected. I would
| be surprised if any part of the C standard did that.)
|
| [1]: I mean, if we're using an x86 derivative we're still
| talking about a very fancy PDP-11 emulator.
| AnimalMuppet wrote:
| I see. The language spec explicitly talks about the
| machine. That's not nothing.
|
| In practice, though, when I have some piece of memory-
| mapped hardware attached, and I want to talk to it, in C
| I can say: *(uint32_t*)0xF00BA4 =
| 0x0102ABCD;
|
| or whatever I need to flip the bits. C lets me actually
| _control_ the whole machine. Whereas Haskell... I don 't
| know, but I suspect it lets me actually _use_ the
| physical machine a lot less.
| yakshaving_jgt wrote:
| You know you can write C inside Haskell, right?
|
| Like, there's literally nothing stopping you. You can use
| FFI, and you can also write C inline.
|
| You can have the best of both, if you want.
| dllthomas wrote:
| There might be a cleaner way of doing it, but
| do let ptr :: Ptr Word32 = nullPtr `plusPtr`
| 0xF00BA4 poke ptr 0x0102ABCD
|
| should have you covered.
| tome wrote:
| > C lets me actually control the whole machine
|
| Really? Can you run micro-ops?
| kachnuv_ocasek wrote:
| I'm sorry to break it to you but every single programming
| language in existence ignores the physical reality of the
| machine. That's the point of abstractions such as programming
| languages.
| a_wild_dandan wrote:
| Also true. Their point is that Haskell's system ignorance
| goes much deeper than its peers.
| kqr wrote:
| I mean yes, it's a higher level language. Python's system
| ignorance goes deeper than, say, Ada's, which in turn goes
| deeper than C++'s, which goes deeper than C's, which goes
| deeper than many of its predecessors, which go higher than
| x86, which goes deeper than PDP-11, which goes deeper than
| logic gates, which go deeper than transistors.
|
| But what's the point? Which languages do we reject because
| they are sufficiently dissimilar to transistors? Should we
| all start writing code in VHDL?
| markusde wrote:
| (jokingly) yes, in Haskell: https://clash-lang.org
| jaggederest wrote:
| These days even binary instructions ignore the reality of the
| physical hardware, as far as I know (I make a javascript
| lol). The output of e.g. assembler is an instruction set for
| a virtual machine that doesn't exist, that the CPU translates
| into actual execution. At least on the intel superscalar
| side, ARM may be a simpler setup.
| bluetomcat wrote:
| There are abstractions which build on the inherently stateful
| nature of computers with their instruction pointers,
| registers, memory and peripheral devices, and there are
| abstractions which coerce you into framing any computational
| problem like a mathematical formalism.
| kqr wrote:
| Haskell's abstractions build on the inherently stateful
| nature of computers - how do you think Haskell compilers do
| their job?
|
| (Not to mention that Haskell and its base libraries have
| plenty of abstractions useful also for the programmer to
| deal with the inherently stateful nature of computers, e.g.
| the IO type, STM, State (it's in the name!), Channels,
| etc.)
| javajosh wrote:
| Yes, this is essentially it. There's a _shape_ to the
| causal connections in the real machine that must be
| respected at higher levels of abstraction. In particular,
| the shape I mean is that basic mechanism of computation
| where you have a program counter, instructions and data in
| mutable memory (von Neumann), and a CPU with registers that
| "starts on the upper left" of memory, and leaves
| interesting shaped smears behind when it's done.
|
| On top of this machine shape the OS adds a process
| abstraction, and a method to speak to devices. It is not
| coincidence that this process shape looks like the machine
| shape: lines of source correspond to instructions, declared
| structures correspond to main memory.... And from here we
| programmers pick a coordinate system and begin to build.
| But whatever coords we pick the space, the degrees-of-
| freedom, always the same: as vast as Turing could fathom.
| The interesting part of coordinate systems is the kinds of
| shapes you get for the constraints you picked. But Haskell
| seems to be a coordinate system with some valid constraint
| ideas (clear division between purity and side-effect,
| immutability), but an invalid sense of its identity as
| merely one coordinate system within this larger structure.
| msla wrote:
| Abstracting over the physical reality of the machine is part of
| the point. The physical reality of the machine isn't the focus
| in programming language design or theory, and it certainly
| isn't the focus of making maintainable code with properties
| like referential transparency, type correctness, and
| parallelizability. Abstractions, in short, allow us to make
| anything worth making.
|
| The machine has no types. The machine has no variables. The
| machine has no functions, procedures, scoping, or information
| hiding. The machine has no assembly language. The machine has
| no machine code. The machine, ignoring the physical reality and
| focusing on an abstraction which could still potentially be in
| the realm of software and not physics, has a certain number of
| bits in flip-flops perturbed by other bits coming in on pins.
|
| > Haskell's power users famously don't actually make anything
| with it (modulo pandoc and jekyll)
|
| Self-contradiction is self-negation. You've destroyed your own
| argument, such as it was.
| Laaas wrote:
| You can apply your argument to almost _any_ language. Haskell's
| semantics not matching the underlying machine has little to do
| with any of the issues in the article.
|
| To the contrary, the simplicity of Haskell allows you to
| understand through simply rewriting expressions according to
| the rules/definitions you define. You don't have to worry about
| memory/effects/so many other things that have nothing to do
| with the _logic_ of what you are trying to do.
|
| Of course, programs in reality often need to be changed to
| improve performance, but this isn't relevant when teaching.
| ipnon wrote:
| One of the benefits of the Curry-Howard isomorphism is that
| people like myself who never make anything useful can use
| computers too.
| outworlder wrote:
| > Abstractions can be beautiful, but they are ephemeral,
| changeable, unreal
|
| Where can we find 'no abstractions' these days? Even if you
| write in ASM, there will be tons of abstractions. Instructions
| will run out of order. Memory is abstracted. Even the ASM you
| write will be translated to microcode.
|
| The closest you'll ever find to 'the physical reality of a
| machine' are microcontrollers (and even then, only some of
| them) and machines from the 80s. I have one sitting right next
| to me that I can tell you exactly how many cycles every CPU
| instruction takes.
|
| Everything else is an abstraction. C abstracts a machine that
| doesn't exist(it was closer to machines that did exist at the
| time it was created). Even something as simple as a short
| circuit expression in your IF statement is an abstraction. Even
| in C you have to sometimes fight the abstractions when you are
| trying to, say, use caches effectively.
| OkayPhysicist wrote:
| Your language should not care about the physical reality of the
| machine. That's the compiler's job, and the CPU microcode's
| job. And thankfully, every programming language ignores
| physical reality, including Assembly.
|
| The goal of a programming language is to allow a human to
| express a sufficiently rigorous solution to a problem. From
| there, every step along the chain of execution is allowed to
| make 'unobservable' (for various definitions of the word)
| changes to execution. Your compiler might unroll your loops, or
| eliminate some unneeded intermediate variable, or even replace
| your entire function with a lookup table. Your CPU's microcode
| might do some weird fuckery with predictive execution. You
| shouldn't care, as long as the solution is, as far as you can
| observe, identical to your given one.
|
| Whether functional programming is a better expression of
| computation than imperative programming is its own problem, but
| it's both silly and wrong to assert that imperative is better
| _because it matches the behavior of the machine_.
| markusde wrote:
| I don't think that this is right. A programming language is
| useful to programmers if it's oriented around the structure of
| the _problem_ and not just the structure of the _physical
| machine_. For some tasks these coincide (especially if you care
| about performance) but I frequently find myself in situations
| where functional code is simple and the machine is irrelevant.
| iamnotsure wrote:
| One of the more surprising aspects of GHC Haskell is that it is
| possible to write a very high level code with performance
| matching or exceeding code written in a low level language,
| thus honoring the machine. Stream fusion for an example. Not
| sure if there is any other language with higher
| abstraction/performance ratio.
| a_wild_dandan wrote:
| JavaScript comes to mind. Its benchmarks are a wonderful
| testament to the immense engineering resources poured into
| V8.
| nequo wrote:
| V8 is very impressive. But in exchange for its speed, it
| needs more memory. JS code optimized for speed tends to use
| more memory with Node.js than optimized Haskell or OCaml
| code:
|
| https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/...
| justincredible wrote:
| [dead]
| jy14898 wrote:
| PureScript might be worth considering, a few of the downsides
| listed here aren't in PS, for example: Int/Number primitives
| aren't overloaded, strict evaluation, the various tools like
| package management are easy, explicit Prelude means you are free
| to import foldl from Array for example.
|
| Of course PureScript has it's own downsides not apparent in GHC
| Laaas wrote:
| PureScript also has the huge advantage that it's trivial to
| build "something". When teaching Haskell, I'm never sure what
| to build as an example. CLI tools aren't attractive, making a
| webserver is complex, and so is making a native UI. Of course
| you can use GHCJS, but at that point, why not just teach
| PureScript in the first place?
| tome wrote:
| Or use gloss and make "something" arguably more easily than
| in PureScript?
|
| https://hackage.haskell.org/package/gloss
| mprovost wrote:
| This is great and a lot of it rings true to my experience writing
| a book to teach Rust. It's basically a giant topological sorting
| exercise to find the optimal order to introduce syntax so that
| you steer clear of rabbit holes. Or you just end up drawing the
| owl.
|
| For example, to implement a simple "hello world" program in Rust
| you have to use a macro (println!), so you can't even look for a
| function signature in the standard library docs to help. So you
| can either just say "don't worry about this for now, just trust
| me" or spend a whole chapter diving into macro syntax. The number
| of concepts you need to implement a basic program is pretty large
| and you could easily spend a chapter going into any of them.
|
| Personally I'm not a fan of the approach in this post to just
| "lie" to people but I do find myself showing a non-optimal
| implementation because that's all the syntax I've introduced up
| to that point. Then later I show how to do it better. I know some
| readers just want the final answer up front though.
| tikhonj wrote:
| Code World[1] is a great project that addresses a number of the
| problems from the article, with an eye towards using Haskell to
| teach children basic math and programming simultaneously. Code
| World directly addresses a number of the obstacles outlined in
| this article:
|
| 1. Using an online editor with a rich built-in library removes
| any toolchain problems.
|
| 2. A custom standard library simplifies pedagogically unnecessary
| details like Foldable
|
| 3. The custom standard library also avoids currying (f(a, b) for
| functions rather than f a b)
|
| 4. Custom error messages improve the feedback students get from
| the compiler
|
| I would highly recommend Code World to anybody looking to teach
| programming _with_ Haskell. If you want to teach Haskell in a way
| that fits the existing ecosystem, it 's also possible to run Code
| World without the custom standard library[2].
|
| [1]: https://code.world/#
|
| [2]: https://code.world/haskell#
| yamtaddle wrote:
| I find its syntax & idiomatic style incredibly difficult to
| follow, in a way nearly no other languages have been for me,
| including some functional languages (OCaml doesn't seem nearly as
| bad to me, for instance).
|
| It's sometimes implied that those who trip over Haskell just
| aren't big-brained enough to understand various important
| concepts related to it, but I've found they're usually very easy
| to grasp, provided the explanation's not using Haskell examples.
| If all programming were Haskell, I probably never would have
| become a programmer in the first place. Would have taken me too
| long to figure any of it out, probably would have concluded I
| wasn't smart enough to be a programmer.
|
| I do wonder if there are some shared experiences or common
| patterns to who tends to love Haskell, and those who don't. I
| also feel nigh-dyslexic trying to read math formulas. Human
| language and broadly C-family programming languages, on the other
| hand, seemed easy and natural to me, almost effortless to pick
| up. Wonder if there's a "mathy"-person versus "languagey"-person
| divide on finding Haskell legible.
|
| I'm not sure it's the whole thing, but I think I've also figured
| out that I find algorithm-type reasoning far easier to follow and
| work with than equations or proofs. Like, the only way I can
| begin to get traction with an unfamiliar equation is to break
| down what each term and operation "does" to something "moving
| through" it--it's tedious as hell. Might be something there.
| yoyohello13 wrote:
| My original degree was in Math and I can definitely 'feel' a
| difference when reading Haskell vs other languages.
|
| Writing/Reading Haskell gives me a similar feeling to doing
| proofs than programming.
|
| Even other functional programming languages don't give me that
| 'in the math class' feeling that Haskell does.
| alpaca128 wrote:
| I'm in the exact same boat. Haskell code feels more like
| abstract maths and I feel more at home when I can just easily
| track the data flow. The language and community uses relatively
| abstract terminology due to its roots and it's just a bit too
| cryptic to me.
|
| Though I'm glad newer languages are starting to adopt more
| features from the functional territory for the situations where
| it just makes more sense.
| jokoon wrote:
| I use generators, list comprehension and a lot of lambda stuff
| with python.
|
| It's a bit fun because it's very short to write, it's concise
| and it helps a lot working only with dict and tuples etc.
|
| Not sure if it's faster, but it's always a bit longer to write
| and think about, and I'm not sure it's easier to read and
| understand.
|
| Sometimes it feels a bit like code golfing, because you can do
| a lot of things with very few lines.
|
| It's immensely better to remove 99% of side effects, the code
| is shorter and more compartmentalized, so it's just easier to
| deal with.
|
| Although I'm doing this alone, and I'm not confident that I
| could enforce this sort of software design in a team.
| paddw wrote:
| I suspect you are right that there's a type of person Haskell
| feels very intuitive to. I think if your mind works that way
| you might have a hard time appreciating the degree of confusion
| "regular" programmers face when trying to decipher the mess of
| symbolic soup.
| yakshaving_jgt wrote:
| There is no reason for Haskell to be a "mess of symbolic
| soup".
|
| You can make Haskell about as human-readable as Ruby if you
| choose to.
| WorldMaker wrote:
| Syntactically Haskell and OCaml are extremely close. A lot
| of the surface difference between the two languages has
| nothing to do with syntax and has so much more to do with
| how things are named by default in the standard libraries.
| (There are massive differences in the type system and all
| sorts of subtle differences below the surface, of course.)
| Hirrolot wrote:
| I find the terminology that Haskell uses quite misleading for
| software engineering. It borrows concepts from category theory
| with quaint names such as a "monad", "endofunctor",
| "catamorphism", etc. The problem is that, instead of a "monad",
| we can say "brrrdogcogfog" and nothing will change -- the name
| is absolutely irrelevant to the problem being solved. Given
| that a monad is an interface for sequential computation, a much
| better name would be something like "Seq", "SeqComp", or
| something like that.
| consilient wrote:
| > Given that a monad is an interface for sequential
| computation, a much better name would be something like
| "Seq", "SeqComp", or something like that.
|
| Just because you can look at something as describing a
| computation doesn't mean you always should. For example:
| data BinaryTree x = | Leaf x | Node
| (BinaryTree x) (BinaryTree x) instance Monad
| BinaryTree where return :: a -> BinaryTree a
| return x = Leaf x bind :: (a -> BinaryTree
| b) -> BinaryTree a -> BinaryTree b bind f (Leaf
| x) = f x // replace a leaf with the result of calling f on
| its label bind f (Node l r) = Node (bind f l)
| (bind f r) // traverse down the tree, ultimately replacing
| all the leaf nodes with a new subtree
|
| You can choose to interpret a binary tree as describing
| nondeterministic computation where you have two choices at
| every step, but I rarely do. Most of the time trees are just
| trees.
| pharmakom wrote:
| F# calls them Computation Expressions which is far more
| approachable imo
| nequo wrote:
| That sounds almost as vague to me as "object" does in OOP.
| Don't non-monadic functions also consist of expressions
| that describe computations?
| truculent wrote:
| As I understand it, monads help solve the problem of
| sequential computation in Haskell but the concept is not
| limited to that. For example, how would you consider the
| monadic properties of data type like Maybe or Either to be
| (exclusively?) interpreted through the lens of sequential
| computation? What about commutative monads where the order
| doesn't matter?
| CyberDildonics wrote:
| _monads help solve the problem of sequential computation in
| Haskell_
|
| It probably confuses people because this is a problem
| haskell created for itself.
| solomonb wrote:
| The `Monad` instance for `Maybe` and `Either` is precisely
| for doing sequential computation!
|
| Consider the following: myBigSubroutine
| :: Maybe Int -> Maybe Int -> Maybe Bool
| myBigSubroutine ma mb = do a <- ma b <-
| mb return (a > b)
|
| Here we are sequencing the "effect" of optionality. `ma`
| must be evaluated before `mb` and if it returns a `Nothing`
| then we short circuit and do not evaluate `mb`.
| gemstones wrote:
| I think the commenter who mentioned syntax is on to
| something. If I write this fn
| my_big_subroutine(ma: Option<isize>, mb: Option<isize>)
| -> Option<bool> { match (ma, mb) {
| (Some(a), Some(b)) => Some(a > b), (_, _)
| => None, } }
|
| it's clearer to me what the intent is. I'm not sure why
| the other syntax is so hard for me but it feels hard to
| understand for some reason.
| truculent wrote:
| > `ma` must be evaluated before `mb`
|
| No - either one can be evaluated first, with the other
| being short-circuited. If you swap the order of those
| lines, the function is exactly the same (in terms of
| inputs and outputs, at least).
| solomonb wrote:
| This `do` syntax desugars to binds like:
| ma >>= \a -> mb >>= \b -> return (a > b)
|
| We can then inline the definition of `>>=` and `return`
| to get: case ma of Nothing ->
| Nothing Just a -> case mb of
| Nothing -> Nothing Just b -> Just (a > b)
| throwaway17_17 wrote:
| tl;dr -> I agree that the terminology is probably not
| something that enhances cohesion amongst devs using Haskell,
| and certainly can be distracting in a pedagogical setting.
|
| I think the fact your experience leads you to believe that a
| monad is an interface for sequential computation. A monad is
| often used for ordering computations, but Haskell's monads
| can also be commutative (like the Reader instance of monad)
| which do not order anything.
|
| The real issue is that the naming convention where some
| typeclass is named after a concept in category theory means
| wildly different things to different developers. For
| instance, I would expect a type/typeclass named for some
| categorical construct to behave in the way the categorical
| construct behaves and that would be the extent of what I use
| it for. However, some developer may see a particular usage
| the same construct and extrapolate that said construct is
| intrinsically tied to that algorithmic pattern of usage.
|
| So the problem is controlling expectation and managing
| consistency throughout the dev community. I doubt Haskell
| will ever get away from the category theory inspired
| libraries and the subsequent naming conventions. See the
| relatively lively development of the profunctor optics based
| work. But, I can certainly see how it may distract or confuse
| newcomers.
| medstrom wrote:
| Like a Javascript ArrayBuffer? No, a Stream?
| viscanti wrote:
| A monad is just a monoid in the category of endofunctors.
| Would most people know what "brrrdogcogfog" means? Couldn't
| we make that argument about literally any word? I don't see
| why it applies more here than elsewhere. For people who have
| experienced it before, it's straightforward and easy to work
| with, for those who haven't, then there's a learning curve.
| No one would likely have encountered "brrrdogcogfog" before
| and everyone would have to go through the learning curve.
| matheusmoreira wrote:
| > Given that a monad is an interface for sequential
| computation
|
| And what the hell is an interface for sequential computation?
| I think I understand what these Maybe types are and what they
| accomplish but "interface for sequential computation" sounds
| a lot like those buzzwords people mix together that could
| mean anything.
| iamnotsure wrote:
| Had good experience at https://exercism.org/tracks/haskell
|
| I don't think this article is helpful for beginners.
| burnished wrote:
| I think this article's audience is teachers of beginners, not
| beginners themselves. At least the author is writing about
| their experience as a teacher.
|
| Don't know why you thought it would be an article for
| beginners, but good on you for linking a resource regardless.
| [deleted]
| iamnotsure wrote:
| The article is an introduction to the basic concepts of
| Haskell, thus beginners may be considered a target audience.
| However, the style and the content brings to my mind the
| dreaded monad tutorials. I'm not convinced the article is
| about pedagogical downsides of specifically Haskell. It
| mostly reads like a collection of random purported
| gotchas/differences from someone with experience with other
| languages.
| burnished wrote:
| I'm taking my cue from the title of the article and the
| intro - seems pretty certain
| asplake wrote:
| Point 11 surprised me. Not the "go" thing but the "where" syntax
| - I wish more languages had it!
| wnoise wrote:
| Yes. Where is often lovely -- I want to delegate details, and
| not think about them yet, but keep that delegation scoped to
| the function that needs the relevant details.
|
| But calling auxiliary functions "go" is almost always bad
| naming.
| chowells wrote:
| "go" is a fantastic name for communicating that all you're
| doing is exactly what the containing named definition
| promises. It's a lot better than adding "Worker" or "Impl" as
| a suffix of the same name as the parent. It contains no
| additional information because there's no additional
| information to contain - the parent name already says it all.
| So you might as well make it short and a standard idiom.
| [deleted]
___________________________________________________________________
(page generated 2023-04-13 23:03 UTC)