How to check if all of the Maybe fields in a haskell record are nothing?

Viewed 456

If I had a record like:

data MaybeTest = MaybeTest {
  test0 :: String
  test1 :: Maybe Int,
  test2 :: Maybe Float,
  test3 :: Maybe Int,
  test4 :: Maybe String,
  test5 :: Maybe String
  }
  deriving (Typeable, Data, Eq, Show)     

is there a simple way to see if all the maybe fields are Nothing? I would like to return Nothing if the records fit this condition else return Just MaybeTest. I want to avoid my current long method of calling each Maybe field and checking if they are Nothing.

2 Answers

Stuff like this usually requires generics. Data.Data generics are generally the easiest to use. You can define a query that checks all Maybe x fields with the isNothing predicate using:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Maybe (isNothing)
import Data.Data (Data, gmapQ)
import Data.Generics.Aliases (ext1Q)

allNothing :: (Data d) => d -> Bool
allNothing = and . gmapQ (const True `ext1Q` isNothing)

Here, ext1Q constructs a query that applies isNothing to any type matching Maybe b while applying const True to any non-Maybe type. The gmapQ function maps it across all fields of the target type, resulting in a list of booleans that are True for all non-Maybe types and Nothing values but False for all Just x values.

You can test it like so:

data Foo = Foo String (Maybe Int) Char (Maybe Double) deriving (Show, Data)
data Bar = Bar (Maybe [Int]) deriving (Show, Data)

and in GHCi:

λ> allNothing (Foo "x" Nothing 'a' Nothing)
True
λ> allNothing (Foo "x" Nothing 'a' (Just 3.14))
False
λ> allNothing (Bar Nothing)
True
λ> allNothing (Bar (Just [1..10]))
False

With that function available, it's easy to write your desired function:

checkNothing :: (Data d) => d -> Maybe d
checkNothing x | allNothing x = Nothing
               | otherwise    = Just x

giving:

λ> checkNothing (Foo "x" Nothing 'a' Nothing)
Nothing
λ> checkNothing (Foo "x" Nothing 'a' (Just 3.14))
Just (Foo "x" Nothing 'a' (Just 3.14))

Not as concise as generic programming, but closer to Haskell 98 and perhaps more readable is the Maybe monad:

{-# LANGUAGE RecordWildCards #-}
data MaybeTest = MaybeTest {
  test0 :: String,
  test1 :: Maybe Int,
  test2 :: Maybe Float,
  test3 :: Maybe Int,
  test4 :: Maybe String,
  test5 :: Maybe String
  }
  deriving (Eq, Show)

valid :: MaybeTest -> Maybe MaybeTest
valid o@(MaybeTest{..}) = do
    _ <- test1
    _ <- test2
    _ <- test3
    _ <- test4
    _ <- test5
    pure o
Related