3

我试图验证 a RecordwithApplicativesEither Monad. 它工作正常。但我看不到所有错误消息。只有第一个是可见的,因为Right路径Either Monad忽略了它们。

这是我的代码:

import Data.Either (either)
import Text.Printf (printf)

data Record = Record
  { fieldA :: String
  , fieldB :: String
  , fieldC :: String
  } deriving (Show, Eq)

type Err = String
    
setField :: String -> String -> Either Err String
setField field value
  | length value > 0 = Right value
  | otherwise = Left $ printf "value for field %s is to short" field

setFieldA :: String -> Either Err String
setFieldA = setField "fieldA"

setFieldB :: String -> Either Err String
setFieldB = setField "fieldB"

setFieldC :: String -> Either Err String
setFieldC = setField "fieldC"
  
makeRecord :: Either Err Record
makeRecord = Record
  <$> setField "fieldA" "valueA"
  <*> setField "fieldB" "valueB"
  <*> setField "fieldC" "valueC"

makeRecord' :: Either Err Record
makeRecord' = Record
  <$> setFieldA "valueA"
  <*> setFieldB "valueB"
  <*> setFieldC "valueC"

recordFromEither :: Either Err Record -> Maybe Record
recordFromEither r =
  case r of
    Right v -> Just $ v
    Left _ -> Nothing

main :: IO ()
main = putStrLn $ output
  where
    output = case makeRecord of
      Right v -> show v
      Left err -> show err

main' :: IO ()
main' = putStrLn $ either id show makeRecord'

我的问题是如何保留和显示所有错误消息。也许与状态单子?

4

2 回答 2

7

这是因为Either Applicative实例的工作方式。你可以做的是包装Either在一个newtype

newtype Validation e r = Validation (Either e r) deriving (Eq, Show, Functor)

然后再给它一个Applicative实例:

instance Monoid m => Applicative (Validation m) where
  pure = Validation . pure
  Validation (Left x) <*> Validation (Left y) = Validation (Left (mappend x y))
  Validation f <*> Validation r = Validation (f <*> r)

您现在可以使用<$><*>来组合Validation [Err] Record结果。有关更多详细信息,请参阅我关于应用验证的文章。

于 2020-08-10T19:07:02.050 回答
7

To accumulate errors, you need a different Applicative instance for Either. This variant of Either is sometimes called Validation. At least two libraries on Hackage provide a variant of Either with that instance:

-- Standard definition
(<*>) :: Either e (a -> b) -> Either e a -> Either e b
Left e <*> _ = Left e
Right _ <*> Left e = Left e
Right f <*> Right x = Right (f x)

-- "Validation" variant
(<*>) :: Monoid e => Either e (a -> b) -> Either e a -> Either e b
Left e <*> Left e' = Left (e <> e')
Left e <*> Right _ = Left e
Right _ <*> Left e = Left e
Right f <*> Right x = Right (f x)

On this topic, a common point of contention is whether the "validation" variant is compatible with the Monad operations of Either (or whether it should be compatible in the first place):

(u <*> v)   =   (u >>= \f -> v >>= \x -> pure (f x))

I mentioned two libraries above because there are differing opinions on the topic (which I think boil down to there being no agreed upon conventional definition of equality, which itself is a symptom of Haskell having no formal semantics).

  • The validation library says that no compatible monad instance exists, so refrains from defining one.
  • The monad-validate library considers that the law above holds up to a particular notion of equivalence, which is arguably okay to do in the context of error reporting, where the worst that should happen is that you might report fewer error than you'd expect. (The library's documentation also contains a lot of relevant exposition.)
于 2020-08-10T19:15:11.833 回答