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

5ssjco0h  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(145)

我有一个用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示例都将出现在此列表中,而不仅仅是本地定义的那些示例。

hgc7kmma

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发送。

相关问题