Luke Palmer writes about Haskell, functional programming, and software design ideas such as the Omega monad and the existential typeclass antipattern.
HN commenters cite his existential typeclass antipattern post when discussing overuse of type classes, and link his posts on tricks like the reverse state monad.
In fact, yes! This is counterintuitive if you think of the floats as a model for the reals, but in fact the floats give the wrong intuition, being (a finite subset of) rationals. [1] details this and refers to two related articles (particularly [2], which I find a lighter read). This fact is also remarked on in [3] (by example of the signum function being uncomputable).
For the particular example of a step function, consider the function f which is 1 on the positive reals (>=0) and 0 otherwise. What representation of the real numbers would you choose for the computation of f(0) to terminate in a finite amount of time? It cannot be the binary representation, as those are infinite. Your algorithm, terminating in a finite amount of time, will have checked only a finite number N of binary digits of the argument, and so I can choose x = 0.0{..N..}01 and obtain f(0) = f(x) by this algorithm, which is incorrect. You can choose the Cauchy sequence representation, or the Dedekind cut one, but this problem will persist (and [1] proves this in general).
You can "cheat" by saying that the leading two bits will store the sign of the number: 00 for 0, 01 for positive numbers and 10 for negative ones. But then suddenly arithmetic operations are not computable (see comments on [2])!
[1] https://lukepalmer.wordpress.com/2008/08/11/all-functions-are-continuous-always/
[2] http://math.andrej.com/2006/03/27/sometimes-all-functions-are-continuous/
[3] Vereshchagin, N., Shen, A. Computable…
Obviously it doesn't actually violate the laws of causality. However, it looks like it does.
At a high level, I think this is essentially similar to the "time travel" you can get with continuations[1]. The first blog post[2] linked in the readme should make things clearer.
[1]: http://matt.might.net/articles/programming-with-continuations--exceptions-backtracking-search-threads-generators-coroutines/
[2]: http://lukepalmer.wordpress.com/2008/08/10/mindfuck-the-reverse-state-monad/
Essentially, a "stateful value"--that is, a value that could be based on some state coupled with that state--is just a function from the state to the value and the new state. It has the type s → (α, s) where s is the type of the state and α is the type of the value. This is the simplest way to represent state in a purely functional way. To actually use it, you have to supply the initial state and you then get the value and the new state.
When you actually use it, you plug the state in to get the value and the next state. Then you go to the next function you want to run, plug the new state into that and so on. The whole program looks neat thanks to do-notation:
stateful = do currentValue ← get
put (currentValue + 1)
return ("x_" ++ currentValue)
If the current state is 0, stateful gets 0, increments the state and returns "x_0" wrapped in the new state (1). If you sequence two statefuls one after another, you get "x_0" followed by "x_1".
The trick is…
I don't know about that. When you get to the "second-level" stuff, it can be weird.
Lately, partially as an exercise and partially to get a project of mine going the way I'd like to see it going, I've been working with the State monad directly. Using the state monad is trivial. Understanding how the state monad does its thing is slightly less trivial, but just a matter of following along the definitions and doing the appropriate substitutions. Understanding where the state monad came from and how to modify it to do what I want... now that's starting to get tricky.
I blundered around for a bit trying to reconstruct the state monad without actually looking at it and came to the conclusion it wasn't possible. Except that it clearly is possible because it's part of the standard library. The problem was that it had never really occurred to me to put a function in the type signature itself, the way the state monad does, and despite how simple it looks it has really hit home that I don't really grok the thing yet. I do not mean to overmystify; like I said I follow the thing perfectly well, a minimal and perfectly functional definition of the thing is like 10 rather simple lines and there's an upper limit to how confusing 10 lines can get, but... if you can't modify something you certainly don't grok it.
Yesterday night I realized I have felt this way before, but it had been such a long time that I hadn't instantly recalled it. I felt this way when I learned about pointers. My…
Do notation is jokingly called a programmable semicolon. True, it is often used to model imperative computations with it, so that is the association people have with it. Also people come mostly from imperative backgrounds and look, we can bind something to a value. Finally something I get! And that is fine, let them play around with it like that.
But if they look closer, they find out it is not really imperative. Why is there a difference between let and bind? Why can't I reassign values. And they start to use more exotic and also more interesting monads and boom the imperative feel goes away.
For example with the interesting LogicT[1] monad. Or what about the probability monad[2]? Or the continuation monad[3]? Or the reverse state monad[4]? How imperative are those really? It really depends how you use it. And I think it adds more to the magic, that you think it is just imperative code at first, because suddenly you can create code that behave very different under various monads that represent computation models. I think that is a cool thing about this confusion.
I understand that if you are a professional that needs to get work done, it can be annoying.
[1]https://hackage.haskell.org/package/logict
[2]https://hackage.haskell.org/package/probability
[3]https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Cont.html
[4]https://lukepalmer.wordpress.com/2008/08/10/mindfuck-the-reverse-state-monad/
Why the focus on type classes? Yes, type classes can sometimes be very helpful (the Num, Monad and Functor typeclasses in Haskell for example - where would we be without them?)
But overuse of type classes in any language is an antipattern. Luke Palmer sums it up well in his blog post [1]
Advanced type system features have their place, but plain old functions go a long, long way. Functions are the masters of reuse: when you use an advanced feature, you need a yet more advanced feature to abstract over it. But all you need to abstract over a function is another function.
Using type classes where they're not appropriate leads to 1. writing unnecessary boilerplate code, 2. torturing the type system and 3. a slippery slope of adding more and more advanced type system features to bludgeon your poor design choice into the right shape. Use functions!
It's also worth reading Gabriel Gonzalez's post on a similar topic [2]
[1] http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/
[2] http://www.haskellforall.com/2012/05/scrap-your-type-classes.html
> If you take FP to its logical conclusion, FP is a slippery slope that eventually leads to Monad-Transformer centric designs.
I don't understand what you mean by this, and I write production haskell for a living. We don't have teetering towers of transformers, and the best advice I've seen is often "put away the shiny tools and just use functions",
https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ . Similarly in http://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html , which is like one real transformer layer and a way of claiming only the capabilities you need in your impure code. His "invert your mocks" article talks about this, too.
Ed Kmett's comments on Scala make me worry that the "solid foundation" isn't as solid as it could be: https://www.reddit.com/r/haskell/comments/1pjjy5/odersky_the_trouble_with_types_strange_loop_2013/cd3bgcu/ . How much of this is true in more recent Scala versions?