A Correspondence Involving Getters and Setters
One of the reasons that I love Haskell is that it leads you to fascinating thought experiments. Here’s one of mine. The conclusions aren’t particularly earth-shattering, but they are interesting.
One of the most common things to do in an imperative programming language is to build getters and setters for the properties of an object. In Java, they may look like this:
public X getFoo();
public void setFoo(X val);
The obvious mapping from there into a purely functional approach gives you this, for a record type R and a field type F:
getFoo :: R -> F
setFoo :: F -> R -> R
The fact that we have two separate functions here is unpleasing to me, though. Without being quite able to explain why, I’d really like to have just one type that completely describes the property “foo”. A product type is definitely cheating… but this would definitely satisfy me, if it works:
foo :: forall t. (F -> (F,t)) -> (R -> (R,t))
I’m interested in this type for two reasons: first, because it’s fairly easy to embed both a getter and a setter together into such a type. Suppose you give me the functions getFoo and setFoo. Then I can certainly embed them into a foo, in such a way that they can be recovered.
foo g r = let (f,v) = g (getFoo r) in (setFoo f r, v)
getFoo’ = snd . foo (\x -> (x,x))
setFoo’ v = fst . foo (\x -> (v,()))
It’s a straight-forward matter of substitution to see that getFoo’ and setFoo’ are identical to their original counterparts. So one can construct a value of the form of foo given any getter and setter combination, and given any such value of the type of foo, one can extract a getter and a setter. The second reason I care about that type, though, is that has a natural meaning aside from just embedding a getter/setter pair. Recall that the State monad (with state type S, for example) is a newtype wrapper around (forall t. S -> (S,t)). So this can be seen as a state transformer. It takes a stateful transformation, and changes the type of the state.
Now, the rather more involved question is whether there exist state transformers (values of the type of foo) that do not arise in that way as the straightforward embedding of getter and setter functions. In other words, could foo be something more than just the encoding of a getter and a setter into a function?
Alas, the answer is yes. It would be nice if the product type of getters and setters were isomorphic to the type of state transformers, and that is very nearly true… but not quite. To see the reasoning work, first note that the type (a -> (b,c)) is isomorphic to (a -> b, a -> c). (This is the type isomorphism version of distributing an exponent over a product). This lets use split up foo into two parts as follows:
foo1 :: forall t. (F -> F) -> (F -> t) -> R -> R
foo2 :: forall t. (F -> F) -> (F -> t) -> R -> t
We can simplify a little by arguing based on the universal quantification. Note that foo1 is given as a parameter a function of type (F -> t), but it cannot possibly make any use of the value, since it does not know anything about the type t. Furthermore, foo2 must produce a value of type t, and can do so only through its parameter of type (F -> t), which can only be used for that purpose. So these turn out to be equivalent to the following simpler types:
modifyFoo :: (F -> F) -> R -> R
filteredGetFoo :: (F -> F) -> R -> F
I’ve named them suggestively, because I have a bit of intuition for what these things tend to mean. Let’s now look at what happens to the getFoo and setFoo functions that we were able to define from the original foo:
setFoo v = modifyFoo (const v)
getFoo = filteredGetFoo id
This all looks as you might expect… but remember that the question is whether modifyFoo and filteredGetFoo are completely determined by the getter / setter pair arising in that way. Clearly they are not. In particular, note that you can iterate a constant function 1 or more times, and always get the same answer no matter the number of iterations; and the identity function similarly for zero or more times. So some interesting logic can be built into modifyFoo or filteredGetFoo with respect to iterating the function passed as a parameter (a constant number of times, or maybe until some predicate holds, or perhaps something more complex), and though this would change the behavior of the modify and filteredGet operations for some inputs, it would have no effect on the get and put operations.
Still, we’ve got something interesting here. I wonder if there are interesting “non-standard” definitions of modify and filteredGet for some common record type. If so, then they would lead to interesting transformations on values of the State monad, which don’t arise from get and set in the normal way. Makes you wonder, doesn’t it?