外壳内有其他图案匹配

时间:2019-08-31 13:44:29

标签: haskell switch-statement

希望该代码的注释足够好。

-- I have 2 data types:

data Person = Person { firstName :: String, lastName :: String, age :: Int }
  deriving (Show)

data Error = IncompleteDataError | IncorrectDataError String
  deriving (Show)

-- This function should take a list a pairs like:
-- fillPerson [("firstName","John"), ("lastName","Smith"), ("dbdf", "dff"), ("age","30"), ("age", "40")]
-- And fill the record with values of the fields with corresponding names.
-- It ignores the redundant fields.
-- If there are less then 3 meaningful fields, it should throw an error IncompleteDataError
-- If the field age doesn't have a number, if should return IncorrectDataError str, where str — is the value of age.

fillPerson :: [(String, String)] -> Either Error Person

fillPerson [] = Left IncompleteDataError
fillPerson (x:xs) = let

  -- Int stores number of fields
  helper :: [(String, String)] -> Person -> Int -> Either Error Person
  helper _ p 3 = Right p
  helper [] _ _ = Left IncompleteDataError
  helper ((key, value):xs) p n = case key of
    "firstName" -> helper xs p{firstName=value} (n + 1)
    "lastName" -> helper xs p{lastName=value} (n + 1)
    -- how to return IncorrectDataError str here?
    -- I need to store reads value :: [(Int, String)]
    -- if the String is not empty, return Left IncorrectDataError value
    -- but how to write this?
    "age" -> helper xs p{age=read value::Int} (n + 1)
    _ -> helper xs p n
  in
    helper (x:xs) Person{} 0

2 个答案:

答案 0 :(得分:3)

您有一个关联列表;使用lookup获取每个名称,如果查找失败,则生成IncompleteDataErrormaybe将每个Nothing转换为Left值,并将每个Just value转换为Right value

-- lookup :: Eq a => a -> [(a,b)] -> Maybe b
-- maybe :: b -> (a -> b) -> Maybe a -> b

verifyInt :: String -> Either Error Int
verifyInt x = ... -- E.g. verify "3" == Right 3
                  --      verify "foo" == Left IncorrectDataError


fillPerson kv = Person
                <$> (get "firstName" kv)
                <*> (get "lastName" kv)
                <*> (get "age" kv >>= verifyInt)
    where get key kv = maybe (Left IncompleteDataError) Right $ lookup key kv

get :: String -> [(String, String)] -> Either Error String起,函数的Applicative实例可确保fillPerson :: [(String, String)] -> Either Error Person。如果对get的任何调用返回了Left IncompleteDataError,则Person <$> ...的结果也将返回。否则,您将获得一个Right (Person ...)值。

答案 1 :(得分:2)

您遇到的问题是尝试在单个递归函数中一次完成所有事情,并涉及多个不同的问题。可以这样写,但最好遵循@chepner的回答格式,然后将其分解成碎片。这是对他们答案的补充。 age的验证。加上导入:

-- readMaybe :: Read a => String -> Maybe a
import Text.Read (readMaybe)

具有帮助功能,可以将Maybe“失败”(Nothing)转换为相应的EitherLeft):

maybeToEither :: a -> Maybe b -> Either a b
maybeToEither x = maybe (Left x) Right

这是一种解决方案,可完成您描述的所有验证:

fillPerson store = do  -- Either monad

  -- May fail with ‘IncompleteDataError’
  f <- string "firstName"
  l <- string "lastName"

  -- May fail with ‘IncompleteDataError’ *or* ‘IncorrectDataError’
  a <- int "age"

  pure Person
    { firstName = f
    , lastName = l
    , age = a
    }

  where

    string :: String -> Either Error String
    string key = maybeToEither IncompleteDataError (lookup key store)

    int :: String -> Either Error Int
    int key = do
      value <- string key  -- Reuse error handling from ‘string’
      maybeToEither (IncorrectDataError value) (readMaybe value)

您可以使用RecordWildCards来使其更紧凑,尽管不建议这样做,因为它不是明确的,因此它对重命名Person中的字段很敏感。

fillPerson store = do
  firstName <- string "firstName"
  lastName <- string "lastName"
  age <- int "age"
  pure Person{..}  -- Implicitly, ‘firstName = firstName’ &c.
  where
    …

Applicative运算符在这种类型的事物上更常见,并且在大多数情况下更可取,因为它们避免了不必要的中间名。但是,使用位置参数而不是命名字段的一个警告是,可以混淆具有相同类型(此处为firstNamelastName)的字段的顺序。

fillPerson store = Person
  <$> string "firstName"
  <*> string "lastName"
  <*> int "age"
  where
    …

也可以使此定义无指向性,使用{将store的参数中的fillPerson省略,而将其改为stringint的参数{1}}(适用于liftA3 Person <$> string "firstName" <*> …);在这种情况下,我不会选择那种样式,但是尝试自己重写它可能是一个值得的练习。


关于您的问题:

(r ->)

您可以写:

    -- I need to store reads value :: [(Int, String)]
    -- if the String is not empty, return Left IncorrectDataError value
    -- but how to write this?

但是您的代码存在许多问题:

  • 它以"age" -> case reads value of [(value', "")] -> helper xs p{age=value'} (n + 1) _ -> Left (IncorrectValueError value) 开头,该字段的字段未定义,如果访问则将引发异常,如果可以保证所有字段均已填充,则可以,但是……

  • 它会跟踪设置的字段的 number ,但不会跟踪哪些字段,因此您可以设置Person三次,最后返回无效的{ {1}}。

因此,如果您想在一个定义中执行此操作,请按照以下方法进行结构重组:保留递归帮助器,但使每个方程式处理一个条件,对每个字段使用带有firstName s的累加器,并在找到每个字段时将其从Person更新为Maybe

Nothing

请注意代码的结构非常脆弱:如果我们完全更改Just,则此定义的许多行都必须更改。这就是为什么最好将问题分解成较小的可组合部分,并将它们放在一起。

但是,这确实是如何将“命令式”循环转换为Haskell的示例:使用累加器为您的“可变”状态编写递归函数,进行递归调用(可能更新累加器)以循环,然后停止递归退出循环。 (实际上,如果您斜视,这本质上是命令式程序到显式控制图的转换。)