繁体   English   中英

Haskell中的多态渲染系统

[英]A polymorphic render system in Haskell

我正在尝试为支持插件的Haskell应用程序编写一种标记语言。 插件编写者不仅应该能够快速使用它,还应该能够扩展其功能并自己创建渲染器。 这就是为什么我创建类Renderable的原因。

class Renderable a b where
  render :: a b -> b

要渲染元素,您可以执行以下操作:

data SomeElement b = SomeElement ...

instance SomeElement SomeGUI where
  render = ...

您还可以创建包含其他元素的元素:

data ListLayout b = ListLayout [b]

instance ListLayout SomeGUI where
  render = ...

最后,只要存在可渲染ab的实例,就可以将任何(ab)渲染为b:

let (myGUI :: b) = render (myLayout :: a b)

当有多个Renderable实例并且我想将相同的值渲染到多个渲染输出时,就会出现问题:

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

编译会推断出布局的类型为(a GuiA),因为GuiA是renderGuiA期望的类型。 结果,由于类型不匹配,renderGuiB显然不会编译。 类似地,尝试为renderGuis提供类型注释根本不起作用。

renderGuis :: (Renderable a GuiA, Renderable a GuiB) => a (GuiA or GuiB) -> IO ()

我正在考虑做这样的事情:

renderGuis :: (Renderable a GuiA, Renderable a GuiB) => a ['GuiA, 'GuiB] -> IO ()

但是,我实际上并没有足够的专业知识,觉得自己在这条路上可能会遇到很多其他问题。

谁能想到一种在不影响功能或可扩展性的情况下完成这项工作的方法? 任何帮助将不胜感激!

您的类型具有要呈现的事物的类型,具体取决于呈现上下文。 因此,例如,您可能拥有一个Button GTK ,例如,它与Button HTML不同。 我认为在大多数情况下这是错误的。 这是执行类型类的另一种方法:

class Renderable a b where
  render :: a -> b -> b

现在,您仍然可以按自己喜欢的方式进行操作(例如, instance Renderable (Button GTK) GTK ),尽管这需要语言扩展。

这是现在可能使用此方法的方式:

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>")

也许更好的课程是:

class Gui a where
  type Config a
  empty :: Config a -> a

class Gui b => Renderable a b where
  render :: a -> Config b -> b

虽然我现在可以做这样的事情,

main :: IO ()
main = do
  let a = render "" (testLayout :: R String) ++ "a"
      b = render (0 :: Int) (testLayout :: R Int) + 2
  return ()

我仍然不能做到以下几点,这就是为什么我问这个问题:

main :: IO ()
main = do
  let savedLayout = testLayout
  let a = render "" (savedLayout :: R String) ++ "a"
      b = render (0 :: Int) (savedLayout :: R Int) + 2
  return ()

我觉得实现这一目标将比预期的要困难得多。 此外,这现在不是问题,而只是以后可能出现的潜在问题。 这就是为什么我现在将结束这个问题并仅在确实必要时才处理该问题。

编辑:渲染为String和Int仅用于测试目的,而不是其实际用途。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM