[英]Additional pattern matching inside case
Hopefully, the code is commented well enough. 希望该代码的注释足够好。
-- 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
You have an association list; 您有一个关联列表; use lookup
to get each name, or produce an IncompleteDataError
if the lookup fails. 使用lookup
获取每个名称,如果查找失败,则产生IncompleteDataError
。 maybe
converts each Nothing
to a Left
value and each Just value
to Right value
. maybe
将每个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
Since get :: String -> [(String, String)] -> Either Error String
, the Applicative
instance for functions ensures that fillPerson :: [(String, String)] -> Either Error Person
. 从get :: String -> [(String, String)] -> Either Error String
fillPerson :: [(String, String)] -> Either Error Person
get :: String -> [(String, String)] -> Either Error String
,函数的Applicative
实例确保fillPerson :: [(String, String)] -> Either Error Person
。 If any call to get
returns Left IncompleteDataError
, the result of Person <$> ...
will do so as well; 如果对get
任何调用返回Left IncompleteDataError
,则Person <$> ...
的结果也将这样做; otherwise, you'll get a Right (Person ...)
value. 否则,您将获得Right (Person ...)
值。
The problem that you have is trying to do all the things at once in a single recursive function, interleaving several different concerns. 您遇到的问题是尝试在单个递归函数中一次完成所有事情,并涉及多个不同的问题。 It's possible to write that way, but better to follow the format of @chepner's answer and break things down into pieces. 可以这样写,但是最好遵循@chepner的回答格式并将其分解成碎片。 This is a supplement to their answer re. 这是对他们答案的补充。 the verification of age
. age
验证。 With the addition of an import: 加上导入:
-- readMaybe :: Read a => String -> Maybe a
import Text.Read (readMaybe)
And a helper function to turn Maybe
“failures” ( Nothing
) into the corresponding Either
( Left
): 以及一个帮助程序功能,可以将Maybe
“失败”( Nothing
)转换为相应的Either
( Left
):
maybeToEither :: a -> Maybe b -> Either a b
maybeToEither x = maybe (Left x) Right
Here is a solution that does all the verification you describe: 这是一个解决方案,可完成您描述的所有验证:
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)
You can make this more compact using RecordWildCards
, although this is less advisable because it's not explicit, so it's sensitive to renaming of fields in Person
. 您可以使用RecordWildCards
使其更紧凑,尽管不建议这样做,因为它不是显式的,因此对Person
中字段的重命名很敏感。
fillPerson store = do
firstName <- string "firstName"
lastName <- string "lastName"
age <- int "age"
pure Person{..} -- Implicitly, ‘firstName = firstName’ &c.
where
…
Applicative
operators are more common for this type of thing, and preferable in most cases as they avoid unnecessary intermediate names. Applicative
运算符在这种类型的事物上更为常见,并且在大多数情况下更可取,因为它们避免了不必要的中间名称。 However, one caveat of using positional arguments rather than named fields is that it's possible to mix up the order of fields that have the same type (here, firstName
and lastName
). 但是,使用位置参数而不是命名字段的一个警告是,可以混淆具有相同类型(此处为firstName
和lastName
)的字段的顺序。
fillPerson store = Person
<$> string "firstName"
<*> string "lastName"
<*> int "age"
where
…
It's also possible to make this definition point-free, omitting store
from the parameters of fillPerson
and making it instead a parameter of string
and int
, using liftA3 Person <$> string "firstName" <*> …
(the (r ->)
applicative); 也可以使这个定义没有意义,使用liftA3 Person <$> string "firstName" <*> …
( (r ->)
,从fillPerson
的参数中fillPerson
store
,并使其改为string
和int
的参数。适用); in this particular case I wouldn't choose that style, but it may be a worthy exercise to try to rewrite it yourself. 在这种情况下,我不会选择那种样式,但是尝试自己重写它可能是一个值得的练习。
As to your question: 关于你的问题:
-- I need to store reads value :: [(Int, String)]
-- if the String is not empty, return Left IncorrectDataError value
-- but how to write this?
You can write: 你可以写:
"age" -> case reads value of
[(value', "")] -> helper xs p{age=value'} (n + 1)
_ -> Left (IncorrectValueError value)
However there are a number of problems with your code: 但是,您的代码有很多问题:
It starts with a Person
whose fields are undefined, and will raise exceptions if accessed, which would be fine if you guaranteed that they were all filled in, but… 它以其字段未定义的Person
开头,如果访问则将引发异常,如果您保证所有字段均已填充,那会很好,但是…
It tracks the number of fields set but not which fields , so you can set firstName
three times and end up returning an invalid Person
. 它跟踪设置的字段数 ,但不跟踪哪个字段 ,因此您可以设置三次firstName
并最终返回无效的Person
。
So if you want to do this in a single definition, here's how I would restructure it—keep the recursive helper, but make each equation handle one condition, using an accumulator with Maybe
s for each of the fields, updating them from Nothing
to Just
as you find each field. 因此,如果您想在一个定义中执行此操作,请按照以下方法进行结构调整:保留递归帮助器,但使每个方程式处理一个条件,对每个字段使用一个带有Maybe
s的累加器,将其从Nothing
更新为Just
当您找到每个字段时。
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
Note how the structure of the code is very brittle: if we change Person
at all, many lines of this definition will have to change. 请注意,代码的结构非常脆弱:如果我们完全更改Person
,则此定义的许多行都必须更改。 That's why it's better to break the problem down into smaller composable parts and put them together. 这就是为什么最好将问题分解成较小的可组合部分,并将它们放在一起。
This does, however, serve as an example of how to translate an “imperative” loop into Haskell: write a recursive function with an accumulator for your “mutable” state, make a recursive call (possibly updating the accumulator) to loop, and stop the recursion to exit the loop. 但是,这确实是如何将“命令式”循环转换为Haskell的示例:使用累加器为您的“可变”状态编写递归函数,进行递归调用(可能会更新累加器)以循环并停止递归退出循环。 (In fact, if you squint, this is essentially a translation of an imperative program into an explicit control graph.) (实际上,如果您斜视,这实质上是命令式程序到显式控制图的转换。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.