Haskell 中的多态渲染系统
A polymorphic render system in Haskell
我正在尝试为支持插件的 Haskell 应用编写标记语言。插件编写者不仅应该能够快速使用它,还应该能够扩展它的功能并自己创建渲染器。这就是我创建 class 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 = ...
最后,只要 Renderable a b 的实例存在,您就可以渲染任何 (a b) 到 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
编译推断布局类型为(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
不同。我认为在大多数情况下这是错误的。这是执行 typeclasses 的另一种方法:
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 是:
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 只是为了测试目的,而不是它的实际用途。
我正在尝试为支持插件的 Haskell 应用编写标记语言。插件编写者不仅应该能够快速使用它,还应该能够扩展它的功能并自己创建渲染器。这就是我创建 class 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 = ...
最后,只要 Renderable a b 的实例存在,您就可以渲染任何 (a b) 到 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
编译推断布局类型为(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
不同。我认为在大多数情况下这是错误的。这是执行 typeclasses 的另一种方法:
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 是:
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 只是为了测试目的,而不是它的实际用途。