使用yesod和persistent,我做了我认为处理Markdown数据的方便类型:
{-# LANGUAGE OverloadedStrings #-}
module Utils.MarkdownText where
import Prelude
import Data.Text.Lazy
import Data.Text as T
import Database.Persist
import Database.Persist.Sql
import Text.Blaze
import Text.Markdown
newtype MarkdownText = MarkdownText { rawMarkdown :: T.Text }
instance PersistField MarkdownText where
toPersistValue = PersistText . rawMarkdown
fromPersistValue (PersistText val) = Right $ MarkdownText { rawMarkdown = val }
fromPersistValue _ = Left "invalid type"
instance PersistFieldSql MarkdownText where
sqlType _ = SqlString
instance ToMarkup MarkdownText where
toMarkup = (markdown def) . fromStrict . rawMarkdown
preEscapedToMarkup = toMarkup . rawMarkdown
您可能会在ToMarkup
实例中注意到我使用def
来获取降价参数。如果我想更改这些设置,而不是在此模块中硬编码,我有哪些选择?
我考虑过让MarkdownText
将设置信息作为参数的选项,但还有其他选项(如果有的话)?
答案 0 :(得分:4)
我将简化问题,以便我们只需要核心库。我们希望根据包含Show
和MarkdownText
的某些ExampleSettings
更改prefix
suffix
的方式。
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import Data.Monoid
import Data.String
newtype MarkdownText = MarkdownText { rawMarkdown :: T.Text}
instance IsString MarkdownText where
fromString = MarkdownText . fromString
data ExampleSettings = ExampleSettings { prefix :: T.Text, suffix :: T.Text }
def = ExampleSettings "" ""
emphasise = def { prefix = "*", suffix = "*" }
showWithSettings :: ExampleSettings -> T.Text -> String
showWithSettings set = show . (\x -> prefix set <> x <> suffix set)
instance Show MarkdownText where
show = showWithSettings def . rawMarkdown
main = print $ MarkdownText "Hello World"
如何解决此问题有很多选项,首先是在值级别,然后是类型级别,最后是全局类型级别。
我们可以选择如何进行。最简单的选项是在值级别添加设置。我们将使用MarkdownText
包装设置。
data ConfiguredMarkdownText = ConfiguredMarkdownText {
markdownText :: MarkdownText,
settings :: ExampleSettings }
instance Show ConfiguredMarkdownText where
show t = showWithSettings (settings t) (rawMarkdown . markdownText $ t)
main = print $ ConfiguredMarkdownText "Hello World" emphasise
为方便起见,我们在第一部分为IsString
添加了MarkdownText
个实例。
我们可以在类型级别而不是在值级别上携带我们需要的额外数据。我们向MarkdownText
添加了一个类型参数,以指示要使用的设置。
newtype MarkdownText s = MarkdownText { rawMarkdown :: T.Text}
我们使用类型来表示可能的设置
data Def = Def
data Emphasise = Emphasise
我们可以为确定设置的类型和可能设置的实例添加类型类。
{-# LANGUAGE FunctionalDependencies #-}
class Setting v k | k -> v where
setting :: proxy k -> v
instance Setting ExampleSettings Def where
setting _ = def
instance Setting ExampleSettings Emphasise where
setting _ = emphasise
只要Show
提供MarkdownText s
,我们就可以s
任意Setting
。
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Setting ExampleSettings s) => Show (MarkdownText s) where
show t = showWithSettings (setting t) (rawMarkdown t)
main = print ("Hello World" :: MarkdownText Emphasise)
MarkdownText :: * -> *
需要稍微不同的IsString
实例。
instance IsString (MarkdownText s) where
fromString = MarkdownText . fromString
reflection包提供了一种临时将值与类型相关联的方法。这让我们可以像前面的例子那样做,但不需要自己的类型来表示设置。
import Data.Reflection
我们首先向MarkdownText
添加一个额外的类型参数,与上一节相同。
newtype MarkdownText s = MarkdownText { rawMarkdown :: T.Text}
反射包定义了一个类Reifies
,它几乎与我们为上一节制作的Setting
类相同。这让我们直接跳到定义Show
实例。
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Reifies s ExampleSettings) => Show (MarkdownText s) where
show t = showWithSettings (reflect t) (rawMarkdown t)
我们将定义一个简便的函数来标记MarkdownText
的类型参数
markdownText :: proxy s -> T.Text -> MarkdownText s
markdownText _ = MarkdownText
并完成设置显示ExampleSettings
时要使用的MarkdownText
的示例。我们使用reify :: a -> (forall s. Reifies s a => Proxy s -> r) -> r
提供带有值的值,该值会传回代理值,该代理值已经被赋予了值。
main = reify emphasise (\p -> print (markdownText p "Hello World"))
这比下一节更简单的版本有优势;多个设置可用于具有不同类型参数的MarkdownText
。
main = reify emphasise $ \p1 ->
reify def $ \p2 ->
do
print (markdownText p1 "Hello World")
print (markdownText p2 "Goodbye")
反射包还定义了一个更简单的类Given
。它被定义为class Given a where given :: a
。它表示可以根据值本身的类型确定的值。这允许我们为特定类型提供单个全局配置值,例如ExampleSettings
。我们可以直接编写MarkdownText
的show实例。
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Given ExampleSettings) => Show (MarkdownText) where
show = showWithSettings given . rawMarkdown
我们为given
ExampleSettings
提供了give :: a -> (Given a => r) -> r
。
main = give emphasise $ print (MarkdownText "Hello World")