I am trying to write a Markup language for my Haskell app which supports plugins. Plugin writers should not only be able to use it quickly but also be able to extends its functionality and create renderers themselves. That's why I created the class Renderable.
class Renderable a b where
render :: a b -> b
To render an element you could do:
data SomeElement b = SomeElement ...
instance SomeElement SomeGUI where
render = ...
You can also create elements which hold other elements:
data ListLayout b = ListLayout [b]
instance ListLayout SomeGUI where
render = ...
In the end, you can render any (ab) to b as long as instances of Renderable ab exist:
let (myGUI :: b) = render (myLayout :: a b)
The problem arises when there are multiple instances of Renderable and I want to render the same value to multiple render outputs:
data SomeElement b = SomeElement
instance Renderable SomeElement GuiA
instance Renderable SomeElement GuiB
renderGuiA :: GuiA -> IO ()
renderGuiB :: GuiB -> IO ()
renderGuis layout = do
renderGuiA (render layout)
renderGuiB (render layout)
main :: IO ()
main = do
let layout = SomeElement
renderGuis layout
The compile infers the type of layout to be (a GuiA), since GuiA is the type renderGuiA expects. As a result, renderGuiB obviously won't compile since the types don't match. Similarly, trying to give renderGuis a type annotation does not work at all.
renderGuis :: (Renderable a GuiA, Renderable a GuiB) => a (GuiA or GuiB) -> IO ()
I was thinking of doing something like this:
renderGuis :: (Renderable a GuiA, Renderable a GuiB) => a ['GuiA, 'GuiB] -> IO ()
However, I do not really have the know-how and feel like I could run into a lot of other problems going down this road.
Can anyone think of a way to make this work without compromising functionality or extensibility? Any help would be greatly appreciated!
Your types have the type of thing that is to be rendered depend on the rendering context. So eg you might have a Button GTK
which would be different from a Button HTML
, for example. I think that is in most cases wrong. Here is an alternative way to do the typeclasses:
class Renderable a b where
render :: a -> b -> b
Now you can still do things your old way if you like (eg instance Renderable (Button GTK) GTK
) although this requires language extensions.
Here is how one might now use this:
data HTML = HTML String
instance Renderable Label HTML where
render (L text) (HTML pre) = HTML (pre ++ "<span>" ++ text ++ "</span>")
instance Renderable a HTML => Renderable (ListLayout a) HTML where
render xs (HTML pre) = HTML (pre ++ "<div class=...>" ++ ((\(HTML x) -> x) <$> (\x -> render x (HTML "")) <$> xs) ++ "</div>")
Maybe a better class would be:
class Gui a where
type Config a
empty :: Config a -> a
class Gui b => Renderable a b where
render :: a -> Config b -> b
While I now can do something like this,
main :: IO ()
main = do
let a = render "" (testLayout :: R String) ++ "a"
b = render (0 :: Int) (testLayout :: R Int) + 2
return ()
i still cannot so the following, which was why I asked this question:
main :: IO ()
main = do
let savedLayout = testLayout
let a = render "" (savedLayout :: R String) ++ "a"
b = render (0 :: Int) (savedLayout :: R Int) + 2
return ()
I feel like achieving this will be much more difficult than anticipated. Furthermore, this is not a problem right now, but just a potential issue that could come up later. That's why I will close this question now and deal with this problem only when it is really necessary.
Edit: Rendering to String and Int is just for testing purposes and not what it is actually used for.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.