如何概括具有不同 EntityField 值的列表

How to generalize a list with different EntityField values

我尝试概括 URL 处理,例如 /api/v1.0/events?order=-id,title 对于 RESTful 输出 - 因此结果将按 id desc 排序,并且比通过 title asc

模型文件:

-- models

Event
    title Text
    content Text
    userId UserId
    deriving Eq
    deriving Show

Haskell 文件:

-- Events.hs

text2Order :: Text -> [SelectOpt Event]
text2Order text =
  case lookup textWithNoPrefix keyVal of
    Just val -> [direction val]
    Nothing -> error "wrong order"

  where
    keyVal = [ ("title", EventTitle)
             , ("user" , EventUserId)
             , ("id"   , EventId)
             ]

    textWithNoPrefix = if T.isPrefixOf "-" text
              then T.tail text
              else text

    direction = if T.isPrefixOf "-" text
              then Desc
              else Asc

我好像有两个问题:

  1. 编译器不喜欢 keyVal 作为元组列表,其中第二个值不同
  2. 即使我将 AscDesc 分配给 direction,编译器也不接受它

问题是EventTitleEventUserId不同的类型,所以你不能把他们两个放在同一个列表中。但是,您可以将 EventTitleEventContent 放在同一个列表中——它们的类型都是 EntityField Event Text.

但是,像下面这样的方法应该可行(使用 Yesod 教程中的 Person 示例):

makeSelectOpt :: (Char,Char) -> SelectOpt Person
makeSelectOpt ('f','+') = Asc  PersonFirstName
makeSelectOpt ('f','-') = Desc PersonFirstName
makeSelectOpt ('l','+') = Asc  PersonLastName
makeSelectOpt ('l','-') = Desc PersonFirstName
makeSelectOpt ('a','+') = Asc  PersonAge
makeSelectOpt ('a','-') = Desc PersonAge

makeSelections :: [(Char,Char)] -> [SelectOpt Person]
makeSelections = map makeSelectOpt

您可以像这样分解出 +/- 处理:

updown '+' = Asc
updown _   = Desc

makeSelectOpt' :: (Char,Char) -> SelectOpt Person
makeSelectOpt' ('f',dir)  = updown dir $ PersonFirstName
makeSelectOpt' ('l',dir)  = updown dir $ PersonLastName
makeSelectOpt' ('a',dir)  = updown dir $ PersonAge

如果要进行错误处理,return一个Maybe (SelectOpt Person):

makeSelectOpt'' :: (Char,Char) -> Maybe (SelectOpt Person)
makeSelectOpt'' ('f',dir)  = Just $ updown dir $ PersonFirstName
makeSelectOpt'' ('l',dir)  = Just $ updown dir $ PersonLastName
makeSelectOpt'' ('a',dir)  = Just $ updown dir $ PersonAge
makeSelectOpt'' _          = Nothing

然后:

makeSelectOpts'' :: [(Char,Char)] -> Maybe [SelectOpt Person)
makeSelectOpts'' pairs = mapM makeSelectOpt'' pairs

如果所有对都有效,结果将是 Just [...];如果其中任何一对未被识别,结果将是 Nothing

更新

这是另一种使用存在类型的方法,它看起来更像您的代码:

{-# LANGUAGE RankNTypes #-}

type ApplyToField = (forall t. EntityField Person t -> SelectOpt Person) -> SelectOpt Person

applyToFirstName, applyToLastName, applyToAge :: ApplyToField
applyToFirstName d = d PersonFirstName
applyToLastName d  = d PersonFirstName
applyToAge     d   = d PersonAge

makeSelectOpt''' :: (Char,Char) -> SelectOpt Person
makeSelectOpt''' (fld,d) = fn (updown d)
  where
    table = [ ('f',applyToFirstName), ('l',applyToLastName), ('a',applyToAge) ]
    fn = case lookup fld table of
           Just f -> f
           Nothing -> error "bad field spec"