> all in all i think it requires a lot of discipline that can easily break down, whereas some of the popular imperative languages you can still sort of plug along (=> punt the technical debt) despite that.
Interestingly I have the exact opposite perspective. Writing imperative or OOP code requires me to be excessively disciplined. It is extremely easy to build un-maintainable spaghetti. There is a whole cottage industry of methods for your discipline of choice: Clean, SOLID, TDD, etc. All these disciplines seem to boil down to the same systemic result, push effects to the edges of you program so you can more easily test, evolve and maintain. Functional programming (of the typed variety) tends to allow me to write garbage code, that is testable and can be easily evolved and maintained because the paradigm encourages me to be a good actor.
I've refactored production code in imperative languages and typed functional languages and only one of them allowed me to make HUGE sweeping changes with ease and high confidence.
> There is a whole cottage industry of methods for your discipline of choice: Clean, SOLID, TDD, etc.
I'm not a big OOP fan, but I'm pretty sure Clean and SOLID are principles (or sets of principles), while TDD alone is a practice. I'm also not very familiar with Clean or SOLID, but I'm pretty sure they apply to Haskell as well, and I would expect that Haskell enthusiasts would ordinarily boast that Haskell allows (or encourages) them to write code that is more SOLID with less effort than other languages.
My criticism of Haskell is that there tends to be an obsession with writing the most SOLID/DRY/etc code possible at the expense of productivity. It's a code golf / ego culture.
Yes, this is cultural and not "built into the language" (quotes because I doubt there's a clear distinction between a programming language's culture and the language itself, but that's a debate for another time), but you can't unplug from the culture, because you need the ecosystem to solve any nontrivial problem (so you still have to interface with overly clever code).
Further, even if you could unplug from the culture (perhaps by writing everything in-house under your own improved culture), there are still no 'rails' that encourage naive, flat-footed, dumb code, and that's the kind of code you want 99% of the time. As far as I'm aware, there isn't even a clear standard for writing clear, naive code in Haskell.
SOLID applies only to OOP. Not using classes trivially satisfies the requirements of every letter. Hell, most design patters are trivial if you use functional programming.
Funny because I see fp as tdd^2 + IO on the side (npi) thus bringing all the benefits you like from oop. Only difference is I think a few decades of culture using these bu default leading to terse syntax and idioms.
This is pretty sound advice for any language. You always want most of your code to be dumb. You save high abstraction for things that have a sensible interface and are common enough to need DRYing. Haskell has a higher abstraction ceiling than most languages and there is less trodden ground, so there may be more opportunity to get lost down the rabbit hole. However Haskell2010 is a beautifully simple language that can solve all your problems (maybe with a bit more code than enabling 20 extensions will afford you).
I had a similar experience with a different language at a startup where one programmer particularly would produce the most horrid code, use new features and libraries where they weren't needed, generally overcomplicate everything. He was smart as hell and made that smartness a liability for everyone else. The bottom line in a business is the bottom line; making money. He didn't understand that at all. No level of abstraction was too high, even trivial stuff. Dumb code that did the job well... dream on.
Hopefully we all agree that static types and dynamic types are useful. Those who use hyperbole are attempting some form of splitting. I think the point where we disagree is what the default should be. The truth is this discussion will rage on into oblivion because dynamic types and static types form a duality. One cannot exist without the other and they will forever be entangled in conflict.
Well, I think that static types are much more useful than dynamic ones. Static types allow you to find errors with your program before execution and that is very important. And if you are going to go through the effort of defining types, it is much better to use static types because then you get this additional error checking. Furthermore, with static types the compiler can help in other ways, e.g. by organizing your data in memory much more efficiently.
I am not sure what you mean when you talk about the duality of static and dynamic types. One can exist without the other and most statically typed languages either forbid or strongly discourage dynamic typing.
> Static types allow you to find errors with your program before execution and that is very important.
It depends on how valuable it is in your situation to be able to run a program that contains type errors.
Sometimes it's a net win. If I'm prototyping an algorithm, and can ignore the type errors so I can learn faster, that's a win. If I'm running a startup and want to just put something out there so that I can see if the market exists (or see what I should have built), it's a net win.
Sometimes it's a net loss. If I'm building an embedded system, it's likely a net loss. If I'm building something safety-critical, it's almost certainly a net loss. If I'm dealing with big money, it's almost certainly at least a big enough risk of a net loss that I can't do it.
Forget ideology. Choose the right tools for the situation.
This is a rather glib response. Of course one should choose the right tools for the situation. Personally if I'm prototyping an algorithm I'd rather do it with types so I don't write any code that was clearly nonsense before I even tried to run it.
Personally, I work the same way you do. But I've heard enough people who want the faster feedback of a REPL-like environment to accept that their approach at least feels more productive to them. It may even be more productive - for them. If so, tying them down with type specifications would slow them down, at least in the prototyping phase.
That certainly seems like a reasonable hypothesis to explore and I'm curious to try a Haskell "EDN"-like type as defined in the article to see if that helps me prototype faster!
One can exist without the other and most statically typed languages either forbid or strongly discourage dynamic typing.
It never seemed like that much of a prohibition to me. Dynamic types take one grand universe of "values" and divide it up in ways that (ideally) reflect differences in those values -- the number six is a different kind of thing than the string with the three letters s i x -- but what the types are is sort of arbitrary. Is an int/string pair a different type than a float/float pair? Is positive-integer a type in its own right? Is every int a rational, or just convertible into a rational? What if you have union types? After using enough dynamically typed languages, the only common factor that I'm confident holds across the whole design space is that a dynamic type is a set of values. That means static typing still leaves you free to define dynamic types that refine the classification of values imposed by your static types, and people do program with pre-/postconditions not stated in types. You just don't get the compiler's help ensuring your code is safe with regard to your own distinctions (unless maybe you build your own refinement type system on top of your language of choice).
By a similar process, dynamic typing leaves you free to define and follow your own static discipline even if a lot of programmers prefer not to. This is more or less why How to Design Programs is written/taught using a dynamic language. The static type-like discipline is a huge aspect of the curriculum, but the authors don't want to force students to commit to one specific language's typing discipline.
Dynamic types are definitly more useful. That's why since the last 40 years, we've almost never once had a language without it. That's why Haskell also has dynamic runtime types. Erasing them at runtime, and stopping to check their validity at runtime would be folly.
Static types are an extension, they say, do not allow types to only be defined at runtime when possible. Its not always possible, which is why static typed languages also include a runtime dynamic type system.
The debate is if the benefit of static checks on types outweighs the negative of having to spend time helping the type checker figure out the types at compile time, and limiting the use of construct that are too dynamic for the static checker to understand at compile time. That's the "dichotomy". To which someone proclaims: "Can we have a static type checker which adds no extra burden to the programmer and no limits to the type of code he wants to write?" To which OP has missed the point entirely and simply shown that you can spend more time giving the Haskell type checker info about EDN, and gain nothing since its now neither useful, nor less effort. Which was a bit dumb, but he did remark that he did it for fun and laughs, not for seriousness.
Probably a similar question, but headed in the other direction "Why should I learn assembly?"
You probably shouldn't. But in the process of learning it you might gain a greater understanding and intuition for how and why things work.
It's not clear to me that category theory, functional programming, what have you, provide insight into why things work. In fact, it seems like they might cut the opposite way -- moving from vicissitudes to simple abstractions.
Category theory provides the tools (a language, a set of theorems, etc) to understand why certain things are necessarily true, rather than just "true in practice" or true because of a happy coincidence, similar (IMO) to how complexity analysis gives you the tools to know that merge sort necessarily has a certain baseline performance, rather than just happening to run through all your unit tests quickly.
I'd disagree. Most business logic is just transformation of data structures in disguise. Once you have a sufficiently rich set of data structures this becomes abundantly clear.
Isn’t `multirec` a function for making hylomorphisms? The recursive dividing is the anamorphism, and the combining of the results is the catamorphism.
As written, however, `multirec` does not efficiently compose. For two constructions to be compatible, they’d have to share their decomposition strategy, including `indivisible` and `divide`. `value` probably composes neatly, but `combine` is a little thorny as the current implementation does two jobs: Processing the data and reconstructing the form of the data.
A composable `multirec` does sound interesting, however. Hmmm...
It's exactly a hylomorphism. Here's a possibly more familiar-looking Haskell form:
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE LambdaCase #-}
import Data.Functor.Foldable
import Data.List.Split
data QuadTreeF a r =
NodeF r r r r
| LeafF a
| EmptyF
deriving Functor
builder :: [a] -> QuadTreeF a [a]
builder = \case
[] -> EmptyF
[x] -> LeafF x
xs -> NodeF a b c d where
[a, b, c, d] = chunksOf (length xs `div` 4) xs
consumer :: QuadTreeF a [a] -> [a]
consumer = \case
EmptyF -> []
LeafF a -> [a]
NodeF ul ur lr ll -> concat [ll, ul, ur, lr]
rotate :: [a] -> [a]
rotate = hylo consumer builder
Interestingly I have the exact opposite perspective. Writing imperative or OOP code requires me to be excessively disciplined. It is extremely easy to build un-maintainable spaghetti. There is a whole cottage industry of methods for your discipline of choice: Clean, SOLID, TDD, etc. All these disciplines seem to boil down to the same systemic result, push effects to the edges of you program so you can more easily test, evolve and maintain. Functional programming (of the typed variety) tends to allow me to write garbage code, that is testable and can be easily evolved and maintained because the paradigm encourages me to be a good actor.
I've refactored production code in imperative languages and typed functional languages and only one of them allowed me to make HUGE sweeping changes with ease and high confidence.