如何收集分布在Haskell代码库中的值



我有一个用Haskell编写的web应用程序(在客户端使用ghcjs,在服务器端使用ghc(,我需要一种方法来收集分布在各个模块中的CSS值。目前,我使用一种涉及CssStyle类和模板haskell的技术。当一个模块需要导出一些CSS时,它会为某个类型创建一个CssStyle实例(该类型除了必须是唯一的之外没有任何意义。(在顶层,所有CssStyle实例都是使用模板haskell中的reifyInstances函数检索的。

这种方法至少有两个缺点:必须创建无意义的类型来附加实例,并且必须确保所有实例都导入到扫描并转化为真正CSS的地方。有人能想出一种更漂亮的方法来收集嵌入Haskell代码中的数据吗?

====================

Quelklef要求提供一些演示当前解决方案的源代码:

{-# LANGUAGE AllowAmbiguousTypes, OverloadedStrings, MultiParamTypeClasses, TemplateHaskell, LambdaCase, FunctionalDependencies, TypeApplications #-}
import Clay
import Control.Lens hiding ((&))
import Data.Proxy
import Language.Haskell.TH
class CssStyle a where cssStyle :: Css
-- | Collect all the in scope instances of CssStyle and turn them into
-- pairs that can be used to build scss files.  Result expression type
-- is [(FilePath, Css)].
reifyCss :: Q Exp
reifyCss = do
insts <- reifyInstances ''CssStyle [VarT (mkName "a")]
listE (concatMap (case InstanceD _ _cxt (AppT _cls typ@(ConT tname)) _decs ->
[ [|($(litE (stringL (show tname))), $(appTypeE [|cssStyle|] (pure typ)))|] ]
_ -> []) insts)
data T1 = T1
instance CssStyle T1 where cssStyle = byClass "c1" & flexDirection row
data T2 = T2
instance CssStyle T2 where cssStyle = byClass "c2" & flexDirection column
-- Need to run this in the interpreter because of template haskell stage restriction:
--
-- > fmap (over _2 (renderWith compact [])) ($reifyCss :: [(String, Css)])
-- [("Main.T2",".c2{flex-direction:column}"),("Main.T1",".c1{flex-direction:row}")]

这里的重点是,从这里导入的任何模块中的任何CsStyle实例都将出现在该列表中,而不仅仅是本地定义的实例。

嗯。。。

我并不正式推荐你目前的做法。它以一种非常非正统的方式使用类型类,所以它不太可能完全按照你的意愿行事。正如您已经注意到的,为了使其工作,您需要确保所有CssStyle实例都在范围内,这是一种非常神秘的行为。此外,当前的方法组合不好,我的意思是,与css相关的计算都是在全局上下文中进行的。

不幸的是,我不知道有什么规范的方法可以在编译时执行您想要的操作。

不过,我有一个想法。大多数程序运行在顶级";工业";monads,我想你的程序也可以。你可以用一个新的应用性(而不是monad(F来包装你的工业monad。这个应用程序的作用是允许子程序将其CSS需求传播给调用者。具体地说,会有一个函数style :: Css -> F (),其作用类似于tell在编写器monad中的作用。将工业monad中的操作嵌入到F中也会有启示。然后每个拥有自己CSS的模块导出其API封装的CCD_ 10;这样做可以跟踪CSS需求。将有一个函数compileCss :: F a -> Css,它构建复合CSS样式,并且执行嵌入在F中的任何有效操作。此外,还有一个函数execute :: F a -> IO a,它执行嵌入在F a值中的动作。然后main可以利用compileCss发出CSS,并利用execute单独运行程序。

我承认这有点尴尬。。。将所有现有代码封装在F中充其量也会很烦人。然而,我确实认为,就跟踪效果而言,它至少是正确的

也许正确的答案是使用现有的基于组件的web框架,它允许您在同一位置定义组件标记和样式?其中一些支持发送到静态HTML。

最新更新