我有一个用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}")]
这里的要点是,从这里导入的任何模块中的任何CssStyle示例都将出现在此列表中,而不仅仅是本地定义的那些示例。
1条答案
按热度按时间hgc7kmma1#
嗯......
我并不正式推荐您当前的方法。它以一种非常非正统的方式使用类型类,因此不太可能完全按照您的意愿工作。正如您已经注意到的,为了使它工作,您需要确保所有的
CssStyle
示例都在范围内,这是一种非常神秘的行为。此外,当前的方法不能很好地组合,我的意思是与css相关的计算都发生在全局上下文中。不幸的是,我不知道有什么规范的方法可以在编译时做你想做的事情。
但是,我有一个想法。大多数程序都运行在顶级的“工业”单子中,我想您的程序也是如此。您可以用一个新的 applicative 来 Package 您的工业单子。(不是monad)
F
。此应用程序的作用是允许子程序将它们的CSS需求传播给调用者。具体地说,有一个函数style :: Css -> F ()
,它的作用类似于writer monad中tell
的作用。也有一些启示将工业monad中的动作嵌入到F
中。然后每个有自己CSS的模块都导出它的API Package 的F
;这样做可以跟踪CSS需求。有一个函数compileCss :: F a -> Css
,它构建复合CSS样式,并且 * 不 * 执行嵌入在F
中的任何有效操作。另外,还有一个函数execute :: F a -> IO a
,它执行嵌入在F a
值中的操作。然后main
可以使用compileCss
来发出CSS,并利用execute
单独运行程序。我承认这有点尴尬......用
F
Package 你所有的现有代码最多也会很烦人。但是,我确实认为它至少是 * 正确的 *,因为它跟踪效果。也许正确的答案是使用现有的基于组件的Web框架,它允许您在同一个地方定义组件标记和样式?其中一些框架支持向静态HTML发送。