## The abstraction continues

I got several comments to my lament about my attempts at abstraction in my previous blog post. Two of the comments involve adding an extra argument to`display`. I dont regard this as an acceptable solution; the changes to the code should not be that intrusive. Adding an argument to a function is a change that ripples through the code to many places and not just the implementation of

`display`.

Reiner Pope succeeded where I failed. He split up the operations in Ops into two classes and presto, it works.

data Person t = Person { firstName :: XString t, lastName :: XString t, height :: XDouble t } class (Show s, IsString s) => IsXString s where (+++) :: s -> s -> s class (Num d, IsXString s) => IsXDouble s d where xshow :: d -> s class (IsXDouble (XString t) (XDouble t)) => Ops t where type XString t :: * type XDouble t :: * instance IsXString String where (+++) = (++) instance IsXDouble String Double where xshow = show data Basic = Basic instance Ops Basic where type XString Basic = String type XDouble Basic = Double display :: Ops t => Person t -> XString t display p = firstName p +++ " " +++ lastName p +++ " " +++ xshow (height p + 1)That's neat, but a little fiddly if there are many types involved.

### Another problem

Armed with this solution I write another function.incSpace :: (Ops t) => XDouble t -> XString t incSpace x = xshow x +++ " "It typechecks fine. But as far as I can figure out there is

**no**way to use this function. Let's see what ghci says:

> :t incSpace (1 :: XDouble Basic) :: XString BasicDespite my best efforts at providing types it doesn't work. The reason being that saying, e.g.,:1:0: Couldn't match expected type `[Char]' against inferred type `XString t' In the expression: incSpace (1 :: XDouble Basic) :: XString Basic :1:10: Couldn't match expected type `XDouble t' against inferred type `Double' In the first argument of `incSpace', namely `(1 :: XDouble Basic)' In the expression: incSpace (1 :: XDouble Basic) :: XString Basic

`(1 :: XDouble Basic)`is the same as saying

`(1 :: Double)`. And that doesn't match

`XDouble t`. At least not to the typecheckers knowledge.

In the example of `display` things work because the parameter `t` occurs in `Person t` which is a real type and not a type family. If a type variable only occurs in type family types you are out of luck. There's no way to convey the information what that type variable should be (as far as i know). You can "solve" the problem by adding `t` as an argument to `incSpace`, but again, I don't see that as a solution.

In the paper ML Modules and Haskell Type Classes: A Constructive Comparison Wehr and Chakravarty introduce a notion of abstract associated types. That might solve this problem. I really want `XDouble` and `XString` to appear as abstract types (or associated data types) outside of the instance declaration. Only inside the instance declaration where I provide implementations for the operations do I really care what the type is.

### A reflection on type signatures

If I writef x = xHaskell can deduce that the type is

`f :: a -> a`.

If I instead write

f :: Int -> Int f x = xHaskell happily uses this type. The type checker does

**not**complain as to say "Sorry dude, but you're wrong, the type is more general than what you wrote.". I think that's nice and polite.

Now a different example.

class C a b where x :: a y :: b f z = [x, x, z]What does ghc have to say about the type of

`f`?

f :: (C a b, C a b1) => a -> [a]OK, that's reasonable; the two occurences of

`x`could have different contexts. But I don't want them to. Let's add a type signature.

f :: (C a b) => a -> [a] f z = [x, x, z]What does ghc have to say?

Blog2.hs:9:7: Could not deduce (C a b) from the context (C a b2) arising from a use of `x' at Blog2.hs:9:7 Possible fix: add (C a b) to the context of the type signature for `f' In the expression: x In the expression: [x, x, z] In the definition of `f': f z = [x, x, z] Blog2.hs:9:10: Could not deduce (C a b1) from the context (C a b2) arising from a use of `x' at Blog2.hs:9:10 Possible fix: add (C a b1) to the context of the type signature for `f' In the expression: x In the expression: [x, x, z] In the definition of `f': f z = [x, x, z]Which is ghc's way of say "Dude, I see your context, but I'm not going to use it because I'm more clever than you and can figure out a better type." Rude, is what I say.

I gave a context, but there is nothing to link the `b` in my context to what ghc internally figures out that the type of the two occuerences of `x` should. I wish I could tell the type checker, "This is the only context you'll ever going to have, use it if you can." Alas, this is not how things work.

### A little ML

Stefan Wehr provided the ML version of the code that I only aluded tomodule MkPerson(O: sig type xString type xDouble val opConcat : xString -> xString -> xString val opShow : xDouble -> xString end) = struct type person = Person of (O.xString * O.xString * O.xDouble) let display (Person (firstName, lastName, height)) = O.opConcat firstName (O.opConcat lastName (O.opShow height)) end module BasicPerson = MkPerson(struct type xString = string type xDouble = float let opConcat = (^) let opShow = string_of_float end) let _ = let p = BasicPerson.Person ("Stefan", "Wehr", 184.0) in BasicPerson.display pIn this case, I think this is the natural way of expressing the abstraction I want. Of course, this ML code has some shortcomings too. Since string literals in ML are not overloaded the cannot be used neatly in the display function like I could in the Haskell version, but that's a minor point.

Labels: Haskell, Modules, overloading

## 4 Comments:

Again, it

is"intrusive" in ML to add the argument O to the functor MkPerson. Itis"a change that ripples through the code to many places and not just the implementation". So I don't see why you accept it.I've made a new blog post that I hope shows why I prefer functors and open for this kind of abstraction.

If you use type families to create `C` and `f`, there is no problem.

> class C a where

> Other a

> x :: a

> y :: Other a

> f :: (C a) => a -> [a]

> f = [x,x,z]

Then again, I don't *fully* understand type families yet :)

Again, it is "intrusive" in ML to add the argument O to the functor MkPerson. It is "a change that ripples through the code to many places and not just the implementation". So I don't see why you accept it.cheap electronics

Post a Comment

<< Home