Haskell 附加模式匹配内盒

Haskell 附加模式匹配内盒,haskell,switch-statement,Haskell,Switch Statement,希望代码注释得足够好 -我有两种数据类型: data Person=Person{firstName::String,lastName::String,age::Int} 衍生节目 数据错误=不完整的数据错误|不正确的数据错误字符串 衍生节目 -此函数应采用如下列表: -fillPerson[名字,约翰,姓氏,史密斯,dbdf,dff,年龄,30岁,40岁] -并用具有相应名称的字段值填充记录。 -它忽略了冗余字段。 -如果少于3个有意义的字段,它应该抛出一个错误IncompleteDataEr

希望代码注释得足够好

-我有两种数据类型: data Person=Person{firstName::String,lastName::String,age::Int} 衍生节目 数据错误=不完整的数据错误|不正确的数据错误字符串 衍生节目 -此函数应采用如下列表: -fillPerson[名字,约翰,姓氏,史密斯,dbdf,dff,年龄,30岁,40岁] -并用具有相应名称的字段值填充记录。 -它忽略了冗余字段。 -如果少于3个有意义的字段,它应该抛出一个错误IncompleteDataError -如果字段age没有数字,If应该返回IncorrectDataError str,其中str-是age的值。 fillPerson::[String,String]->任一错误人员 fillPerson[]=左不完全数据错误 fillPerson x:xs=let -Int存储字段的数量 助手::[String,String]->Person->Int->任一错误人员 助手uP 3=右p helper[]\uu0=左不完全数据错误 助手键,值:xs p n=大小写键 firstName->helper xs p{firstName=value}n+1 lastName->helper xs p{lastName=value}n+1 -如何在此处返回不正确的DataError str? -我需要存储读取值::[Int,String] -如果字符串不为空,则返回Left IncorrectDataError值 -但是怎么写呢? age->helper xs p{age=read value::Int}n+1 _->helper xs p n 在里面 助手x:xs个人{}0
你有一个关联列表;使用lookup获取每个名称,如果查找失败,则生成一个不完整的DataError。可能会将每个Nothing转换为左值,将每个Just值转换为Right值


由于get::String->[String,String]->任一错误字符串,函数的应用程序实例确保fillPerson::[String,String]->任一错误字符串。如果对get的任何调用返回Left IncompletedDataError,则Person。。。我们也会这样做;否则,你会找到一个合适的人。。。值。

您有一个关联列表;使用lookup获取每个名称,如果查找失败,则生成一个不完整的DataError。可能会将每个Nothing转换为左值,将每个Just值转换为Right值


由于get::String->[String,String]->任一错误字符串,函数的应用程序实例确保fillPerson::[String,String]->任一错误字符串。如果对get的任何调用返回Left IncompletedDataError,则Person。。。我们也会这样做;否则,你会找到一个合适的人。。。值。

您遇到的问题是,试图在一个递归函数中同时执行所有操作,并将几个不同的关注点交织在一起。这样写是可能的,但最好按照@chepner答案的格式,把事情分解成几部分。这是对他们答案的补充。年龄的验证。添加了一个导入:

-- readMaybe :: Read a => String -> Maybe a
import Text.Read (readMaybe)
以及一个帮助函数,用于将可能的“故障”Nothing转换为相应的左或右:

以下是一个解决方案,它完成了您描述的所有验证:

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)
对于这类事情,应用运算符更常见,并且在大多数情况下更可取,因为它们避免了不必要的中间名称。但是,使用位置参数而不是命名字段的一个警告是,可能会混淆具有相同类型的字段(firstName和lastName)的顺序

你可以写:

"age" -> case reads value of
  [(value', "")] -> helper xs p{age=value'} (n + 1)
  _ -> Left (IncorrectValueError value)
但是,您的代码存在许多问题:

它从一个字段未定义的人开始,如果被访问,将引发异常,如果您保证所有字段都已填写,这将很好,但是

它跟踪设置的字段数,但不跟踪哪些字段,因此您可以设置三次firstName,最终返回一个无效的人

如果你想在一个单一的定义中这样做,我将如何重新构造它,保持递归辅助,但让每个方程处理一个条件,使用一个累加器,为每个字段使用一个带Maybes的累加器,将它们从零更新为与你找到的每个字段一样

fillPerson' :: [(String, String)] -> Either Error Person
fillPerson' = fillFields (Nothing, Nothing, Nothing)
  where

    fillFields
      -- Accumulator of firstName, lastName, and age.
      :: (Maybe String, Maybe String, Maybe Int)
      -- Remaining input keys to check.
      -> [(String, String)]
      -- Final result.
      -> Either Error Person

    -- Set firstName if not set.
    fillFields (Nothing, ml, ma) (("firstName", f) : kvs)
      = fillFields (Just f, ml, ma) kvs

    -- Set lastName if not set.
    fillFields (mf, Nothing, ma) (("lastName", l) : kvs)
      = fillFields (mf, Just l, ma) kvs

    -- Set age if not set, failing immediately if not a valid number.
    fillFields (mf, ml, Nothing) (("age", a) : kvs)
      | all (`elem` ['0'..'9']) a
      = fillFields (mf, ml, Just (read a)) kvs
      | otherwise
      = Left (IncorrectDataError a)

    -- Ignore redundant firstName.
    fillFields acc@(Just{}, ml, ma) (("firstName", _) : kvs)
      = fillFields acc kvs

    -- Ignore redundant lastName.
    fillFields acc@(mf, Just{}, ma) (("lastName", _) : kvs)
      = fillFields acc kvs

    -- Ignore redundant age.
    fillFields acc@(mf, ml, Just{}) (("age", _) : kvs)
      = fillFields acc kvs

    -- Ignore extra fields.
    fillFields acc (_ : kvs)
      = fillFields acc kvs

    -- If all fields are present anywhere in the input,
    -- we can finish early and successfully.
    fillFields (Just f, Just l, Just a) _
      = Right Person
        { firstName = f
        , lastName = l
        , age = a
        }

    -- If any field is missing at the end, fail.
    fillFields __ []
      = Left IncompleteDataError
请注意,代码的结构非常脆弱:如果我们根本改变了Person,那么这个定义的许多行都必须改变。这就是为什么最好将问题分解为更小的可组合部分并将它们组合在一起


然而,这确实是一个如何将“命令式”循环转换为Haskell的示例:为“可变”状态编写一个带有累加器的递归函数,进行递归调用(可能会将累加器更新为循环),并停止递归以退出循环。事实上,如果你眯着眼睛看,这本质上是将命令式程序转换为显式控制图。

你遇到的问题是试图在一个递归函数中同时完成所有事情,并将多个不同的关注点交织在一起。这样写是可能的,但最好按照@chepner答案的格式,把事情分解成几部分。这是对《公约》的补充 我回答你。年龄的验证。添加了一个导入:

-- readMaybe :: Read a => String -> Maybe a
import Text.Read (readMaybe)
以及一个帮助函数,用于将可能的“故障”Nothing转换为相应的左或右:

以下是一个解决方案,它完成了您描述的所有验证:

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)
对于这类事情,应用运算符更常见,并且在大多数情况下更可取,因为它们避免了不必要的中间名称。但是,使用位置参数而不是命名字段的一个警告是,可能会混淆具有相同类型的字段(firstName和lastName)的顺序

你可以写:

"age" -> case reads value of
  [(value', "")] -> helper xs p{age=value'} (n + 1)
  _ -> Left (IncorrectValueError value)
但是,您的代码存在许多问题:

它从一个字段未定义的人开始,如果被访问,将引发异常,如果您保证所有字段都已填写,这将很好,但是

它跟踪设置的字段数,但不跟踪哪些字段,因此您可以设置三次firstName,最终返回一个无效的人

如果你想在一个单一的定义中这样做,我将如何重新构造它,保持递归辅助,但让每个方程处理一个条件,使用一个累加器,为每个字段使用一个带Maybes的累加器,将它们从零更新为与你找到的每个字段一样

fillPerson' :: [(String, String)] -> Either Error Person
fillPerson' = fillFields (Nothing, Nothing, Nothing)
  where

    fillFields
      -- Accumulator of firstName, lastName, and age.
      :: (Maybe String, Maybe String, Maybe Int)
      -- Remaining input keys to check.
      -> [(String, String)]
      -- Final result.
      -> Either Error Person

    -- Set firstName if not set.
    fillFields (Nothing, ml, ma) (("firstName", f) : kvs)
      = fillFields (Just f, ml, ma) kvs

    -- Set lastName if not set.
    fillFields (mf, Nothing, ma) (("lastName", l) : kvs)
      = fillFields (mf, Just l, ma) kvs

    -- Set age if not set, failing immediately if not a valid number.
    fillFields (mf, ml, Nothing) (("age", a) : kvs)
      | all (`elem` ['0'..'9']) a
      = fillFields (mf, ml, Just (read a)) kvs
      | otherwise
      = Left (IncorrectDataError a)

    -- Ignore redundant firstName.
    fillFields acc@(Just{}, ml, ma) (("firstName", _) : kvs)
      = fillFields acc kvs

    -- Ignore redundant lastName.
    fillFields acc@(mf, Just{}, ma) (("lastName", _) : kvs)
      = fillFields acc kvs

    -- Ignore redundant age.
    fillFields acc@(mf, ml, Just{}) (("age", _) : kvs)
      = fillFields acc kvs

    -- Ignore extra fields.
    fillFields acc (_ : kvs)
      = fillFields acc kvs

    -- If all fields are present anywhere in the input,
    -- we can finish early and successfully.
    fillFields (Just f, Just l, Just a) _
      = Right Person
        { firstName = f
        , lastName = l
        , age = a
        }

    -- If any field is missing at the end, fail.
    fillFields __ []
      = Left IncompleteDataError
请注意,代码的结构非常脆弱:如果我们根本改变了Person,那么这个定义的许多行都必须改变。这就是为什么最好将问题分解为更小的可组合部分并将它们组合在一起


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

你能检查年龄字符串是否为数字格式吗?当然,写一个函数verify::string->Error Int,然后使用get age kv>>=verify。你能检查年龄字符串是否为数字格式吗?当然,编写一个函数verify::String->Error Int,然后使用get age kv>>=verify。
fillPerson' :: [(String, String)] -> Either Error Person
fillPerson' = fillFields (Nothing, Nothing, Nothing)
  where

    fillFields
      -- Accumulator of firstName, lastName, and age.
      :: (Maybe String, Maybe String, Maybe Int)
      -- Remaining input keys to check.
      -> [(String, String)]
      -- Final result.
      -> Either Error Person

    -- Set firstName if not set.
    fillFields (Nothing, ml, ma) (("firstName", f) : kvs)
      = fillFields (Just f, ml, ma) kvs

    -- Set lastName if not set.
    fillFields (mf, Nothing, ma) (("lastName", l) : kvs)
      = fillFields (mf, Just l, ma) kvs

    -- Set age if not set, failing immediately if not a valid number.
    fillFields (mf, ml, Nothing) (("age", a) : kvs)
      | all (`elem` ['0'..'9']) a
      = fillFields (mf, ml, Just (read a)) kvs
      | otherwise
      = Left (IncorrectDataError a)

    -- Ignore redundant firstName.
    fillFields acc@(Just{}, ml, ma) (("firstName", _) : kvs)
      = fillFields acc kvs

    -- Ignore redundant lastName.
    fillFields acc@(mf, Just{}, ma) (("lastName", _) : kvs)
      = fillFields acc kvs

    -- Ignore redundant age.
    fillFields acc@(mf, ml, Just{}) (("age", _) : kvs)
      = fillFields acc kvs

    -- Ignore extra fields.
    fillFields acc (_ : kvs)
      = fillFields acc kvs

    -- If all fields are present anywhere in the input,
    -- we can finish early and successfully.
    fillFields (Just f, Just l, Just a) _
      = Right Person
        { firstName = f
        , lastName = l
        , age = a
        }

    -- If any field is missing at the end, fail.
    fillFields __ []
      = Left IncompleteDataError