The generics-sop solution is very nice, but it's not unusual to see this done, in actual practice, with type classes and boilerplate up to some reasonable tuple size. (At least, that's how I see it done in a lot of high-quality library code, like lens.)
So, for completeness, here's a no-generics solution using a type class with an associated type family to define the maybeized type:
{-# LANGUAGE TypeFamilies #-}
class ToMaybe a where
type AsMaybe a
toMaybe :: a -> AsMaybe a
and a series of tedious boilerplate definitions:
instance ToMaybe (a,b) where
type AsMaybe (a,b) = (Maybe a, Maybe b)
toMaybe (a,b) = (Just a, Just b)
instance ToMaybe (a,b,c) where
type AsMaybe (a,b,c) = (Maybe a, Maybe b, Maybe c)
toMaybe (a,b,c) = (Just a, Just b, Just c)
instance ToMaybe (a,b,c,d) where
type AsMaybe (a,b,c,d) = (Maybe a, Maybe b, Maybe c, Maybe d)
toMaybe (a,b,c,d) = (Just a, Just b, Just c, Just d)
-- etc.
giving:
λ> toMaybe (1,3)
(Just 1,Just 3)
λ> toMaybe (1,3,4)
(Just 1,Just 3,Just 4)
Optionally, the boilerplate can be generated with Template Haskell. Due to stage restrictions, we'd need to define a module containing the class itself and the TH instance generator:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module MaybeTuplesHelper where
import Control.Monad
import Language.Haskell.TH
class ToMaybe a where
type AsMaybe a
toMaybe :: a -> AsMaybe a
mkToMaybeInstance n = do
nms <- replicateM n $ newName "a"
let -- typ: (a1, a2, a3)
typ = foldl appT (tupleT n) . map varT $ nms
-- mtyp: (Maybe a1, Maybe a2, Maybe a3)
mtyp = foldl appT (tupleT n) . map (appT (conT (mkName "Maybe")) . varT) $ nms
-- pat: (a1, a2, a3)
pat = tupP . map varP $ nms
-- mexp: (Just a1, Just a2, Just a3)
mexp = tupE . map (appE (conE (mkName "Just")) . varE) $ nms
[d| instance ToMaybe $(typ) where
type AsMaybe $(typ) = $(mtyp)
toMaybe $(pat) = $(mexp)
|]
and then generate the instance themselves (up to 15-tuples here) in a separate module:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import MaybeTuplesHelper
concat <$> mapM mkToMaybeInstance [2..15]
and it seems to work:
λ> :l MaybeTuples
[2 of 2] Compiling Main ( MaybeTuples.hs, interpreted ) [flags changed]
Ok, modules loaded: Main, MaybeTuplesHelper (/scratch/buhr/stack/global-project/.stack-work/odir/MaybeTuplesHelper.o).
Collecting type info for 2 module(s) ...
λ> toMaybe (1,2,3,4,5,"six")
(Just 1,Just 2,Just 3,Just 4,Just 5,Just "six")