【问题标题】:How do I derive PersistField for a custom newtype?如何为自定义新类型派生 PersistField?
【发布时间】:2014-04-28 05:02:03
【问题描述】:

这是一个特定于 Yesod 的问题,但即使您不知道 Yesod 也可以帮助我,它与新类型有关。

假设我的配置/模型中有以下简化模型

Value
  userId       UserId
  weight       Weight
  deriving (Show)

我将在我的 web 应用程序中同时使用公斤和磅,但我决定数据库应该以公斤为单位存储东西。为了让类型系统避免混淆这两者,我定义了以下内容:

newtype Weight = Kilograms Int
  deriving (Read, Show, Eq, PersistField, PersistFieldSql)

编译得很好,但是如何从表单中使用它?

logForm :: UserId -> Form Value
logForm uid = renderDivs $ Value <$>
    pure uid <*>
    areq intField "Weight" Nothing

我得到了错误

No instance for (Integral ModelTypes.Weight)
  arising from a use of `intField'

我尝试派生Integral,但它抱怨我没有Real Weight。不断地,我最终得到:

newtype Weight = Grams Int
   deriving (Read, Show, Eq, Enum, Ord, Num, Integral, Real, PersistField, PersistFieldSql)

这是正确的方法吗?好像有很多重复。有什么更好的方法?

一般来说,如果我在 Haskell 中有一个

newtype N = T a

对于具体类型a,我如何让N 重新派生a 的所有实例,并让N 派生一些其他类型类(在我的示例中为PersistFieldPersistFieldSql)。非常感谢。

【问题讨论】:

    标签: haskell yesod


    【解决方案1】:

    PersistField 与字段不同。您想通过导入 Yesod.Forms 来创建自定义字段。这是一个示例,用于 MathJax 类型 ;)

    newtype MathJax = MathJax { unMathJax :: Markdown }
      deriving (Eq, Ord, Show, Read, PersistField, PersistFieldSql, IsString, Monoid)
    
    unMM :: MathJax -> Text
    unMM = unMarkdown . unMathJax
    
    mathJaxField :: (Monad m,  RenderMessage (HandlerSite m) FormMessage) => Field m MathJax
    mathJaxField = Field
        { fieldParse = parseHelper $ Right . MathJax . Markdown . Text.filter (/= '\r')
        , fieldView  = \theId name attrs val _isReq -> toWidget
          [hamlet|$newline never
            <textarea id="#{theId}" name="#{name}" *{attrs}>#{either id unMM val}
          |]
        , fieldEnctype = UrlEncoded
        }
    

    【讨论】:

    • 您好,感谢您的帮助。你确定这是正确的方法吗,在这种情况下,底层类型只是一个简单的 Int,而不是 Markdown(你必须为此编写自己的 fieldParse/fieldView?)
    • 是的,您将不得不编写自己的字段。字段类型将解析器(fieldParse 记录)和渲染器(fieldView 记录)结合在一起。因此,您需要将您的 URL 编码 Int 解析为 Int,然后解析为您的新类型。同样,您需要解开新类型。但这是一个“最小”的案例。这是不可推导的,大概是因为您可能希望有一种不同的方式来呈现或解析您的类型。
    • 这很奇怪,我在问题中写的确实有效——我通常可以使用intField 并将其保存到数据库中——然后当我从数据库中取回它时,show我,我确实看到该字段包含在Kilograms 中。另一方面,将新类型更改为Kilograms Double,然后适当地使用doubleField,我不断得到Couldn't match type 'Double' with 'ModelTypes.Weight'。为什么它适用于Int 而不是Double,这很奇怪。
    • 我最终像您在此处显示的那样编写了自定义字段,因此接受。如果您想办法将普通的 intField 用于具有底层 Int 的新类型,而不必求助于创建自定义新字段并保持类型安全,请告诉我。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-25
    相关资源
    最近更新 更多