Hi Oleg,

Thanks a lot for your reply. I see now where my attempt went wrong and
why it couldn't work in the first place, the instances will indeed
overlap. I'm not completely satisfied with your solution though, but
seeing how you did it has lead me to the solution I want. Details
below. :-)

] Fortunately, there is a solution that does not involve proxies or
] type annotations. We use a `syntactic hint' to tell the typechecker
] which intermediate type we want. To be more precise, we assert local
] functional dependencies. Thus we can write:
]
]      p c = build "p" [embed c]
]
]      test1 :: XML
]      test1 = p [[p [[p "foo"]]]]
]
] Our syntactic crutch is the list notation: [[x]]. We could have used a
] single pair of brackets, but we'd like to avoid overlapping
] instances (as is done in the following self-contained code).

While I appreciate the ingenuity of the solution, unfortunately I
cannot use it. First of all I don't want to require my users to write
double brackets everywhere, it makes the code a lot uglier IMO.
Another problem is that in my real library (as opposed to the
simplified example I gave here) I allow the embedding of lists, which
means that the [[x]] is not safe from overlap as it is in your
example. But I still see the general pattern here, the point is just
to get something that won't clash with other instances. I could define

 data X a = X a

 instance (TypeCast a XML) => Embed (X a) XML where
   embed (X a) = typeCast a

and write

 test1 = p (X $ p (X $ p "foo"))

Not quite so pretty, even worse than with the [[ ]] syntax.

However, I have an ace up my sleeve, that allows me to get exactly
what I want using your trick. Let's start the .lhs file first:
{-# OPTIONS_GHC -fglasgow-exts #-}
{-# OPTIONS_GHC -fallow-overlapping-instances #-}
{-# OPTIONS_GHC -fallow-undecidable-instances #-}
module HSP where

import Control.Monad.State
import Control.Monad.Writer
import TypeCast -- putting your six lines in a different module

Now, the thing I haven't told you in my simplified version is that all
the XML generation I have in mind takes place in monadic code. In
other words, all instances of Build will be monadic. My whole point of
wanting more than one instance is that I want to use one monad, with
an XML representation, in server-side code and another in client-side
code, as worked on by Joel Björnson.

Since everything is monadic, I can define what it means to be an
XML-generating monad in terms of a monad transformer:

newtype XMLGen m a = XMLGen (m a)
  deriving (Monad, Functor, MonadIO)

and define the Build and Embed classes as

class Build m xml child | m -> xml child where
 build :: String -> [child] -> XMLGen m xml

class Embed a child where
 embed :: a -> child

Now for the server-side stuff:

data XML = CDATA String | Element String [XML]
 deriving Show

newtype HSPState = HSPState Int -- just to have something
type HSP' = StateT HSPState IO
type HSP = XMLGen HSP'

Note that by including XMLGen we define HSP to be an XML-generation
monad. Now we can declare our instances.

First we can generate XML values in the HSP monad (we use HSP [XML] as
the child type to enable embedding of lists):

instance GenXML HSP' XML (HSP [XML]) where
 genElement s chs = do
        xmls <- fmap concat $ sequence chs
        return (Element s xmls)

Second we do the TypeCast trick, with XMLGen as the marker type:

instance TypeCast (m x) (HSP' XML) =>
        Embed (XMLGen m x) (HSP [XML]) where
  embed (XMLGen x) = XMLGen $ fmap return $ typeCast x

And now we can safely declare other instances that will not clash with
the above because of XMLGen, e.g.:

instance Embed String (HSP [XML]) where
 embed s = return [CDATA s]

instance Embed a (HSP [XML]) => Embed [a] (HSP [XML]) where
 embed = fmap concat . mapM embed -- (why is there no concatMapM??)

This last instance is why I cannot use lists as disambiguation, and
also why I need overlapping instances. Now for some testing functions:

p c = build "p" [embed c]

test0 :: HSP XML
test0 = p "foo"

test1 :: HSP XML
test1 = p (p "foo")

test2 :: HSP XML
test2 = p [p "foo", p "bar"]

All of these now work just fine. We could end here, but just to show
that it works we do the same stuff all over again for the clientside
stuff (mostly dummy code, the clientside stuff doesn't work like this
at all, this is just for show):

data ElementNode = ElementNode String [ElementNode] | TextNode String
 deriving Show

type HJScript' = WriterT [String] (State Int)
type HJScript = XMLGen HJScript'

instance Build HJScript' ElementNode (HJScript ElementNode) where
 build s chs = do
        xs <- sequence chs
        return $ ElementNode s xs

instance TypeCast (m x) (HJScript' ElementNode) =>
    Embed (XMLGen m x) (HJScript ElementNode) where
 embed (XMLGen x) = XMLGen $ typeCast x

instance Embed String (HJScript ElementNode) where
 embed s = return $ TextNode s

Testing the new stuff, using the same p as above:

test3 :: HJScript ElementNode
test3 = p "foo"

test4 :: HJScript ElementNode
test4 = p (p "foo")

And these also work just as expected! :-)

Thanks a lot for teaching my the zen of TypeCast, it works like a
charm once you learn to use it properly. Really cool stuff! :-)

/Niklas
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to