The abstraction continues

display

display

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. 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

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)

Another problem

incSpace :: (Ops t) => XDouble t -> XString t incSpace x = xshow x +++ " "

> :t incSpace (1 :: XDouble Basic) :: XString Basic :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)

(1 :: Double)

XDouble t

That's neat, but a little fiddly if there are many types involved.Armed with this solution I write another function.It typechecks fine. But as far as I can figure out there isway to use this function. Let's see what ghci says:Despite my best efforts at providing types it doesn't work. The reason being that saying, e.g.,is the same as saying. And that doesn't match. 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

f x = x

f :: a -> a

If I writeHaskell can deduce that the type is

If I instead write

f :: Int -> Int f x = x

Haskell happily uses this type. The type checker doescomplain 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]

f

f :: (C a b, C a b1) => a -> [a]

x

f :: (C a b) => a -> [a] f z = [x, x, z]

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]

What does ghc have to say about the type ofOK, that's reasonable; the two occurences ofcould have different contexts. But I don't want them to. Let's add a type signature.What does ghc have to say?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

module 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 p

Stefan Wehr provided the ML version of the code that I only aluded toIn 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