More often than not I am writing functions that are stripping the only constructor of a new type, such as in the following function to return the first argument that is not Nothing:
process (Pick xs) = (\(First x) -> x) . mconcat . map (First . process) $ xs
I think the lambda is unnecessarily verbose. I would like to write something like this:
process (Pick xs) = -First . mconcat . map (First . process) $ xs
Do the meta programming facilities of Haskell allow for anything similar to this? Any other solution to solve this problem in a more concise way is also welcome.
UPD. The whole code has been requested:
data Node where
Join :: [Node] -> Node
Pick :: [Node] -> Node
Given :: Maybe String -> Node
Name :: String -> Node
process :: Node -> Maybe String
process (Join xs) = liftM os_path_join (mapM process xs)
process (Pick xs) = getFirst . mconcat . map (First . process) $ xs
process (Name x) = Just x
process (Given x) = x
In this case you can actually use the newtypes
package to solve this problem more generically:
process :: Node -> Maybe String
process (Pick xs) = ala' First foldMap process xs
process (Join xs) = liftM os_path_join (mapM process xs)
process (Name x) = Just x
process (Given x) = x
You could even have a more generic version that takes a Newtype n (Maybe String)
like
process'
:: (Newtype n (Maybe String), Monoid n)
=> (Maybe String -> n) -> Node -> Maybe String
process' wrapper (Pick xs) = ala' wrapper foldMap (process' wrapper) xs
process' wrapper (Join xs) = liftM os_path_join (mapM (process' wrapper) xs)
process' wrapper (Name x) = Just x
process' wrapper (Given x) = x
Then
> let processFirst = process' First
> let processLast = process' Last
> let input = Pick [Given Nothing, Name "bar", Given (Just "foo"), Given Nothing]
> processFirst input
Just "bar"
> ProcessLast input
Just "foo"
As an explanation for how this works, the ala'
function takes a newtype wrapper to determine the instance of Newtype
to use, a function which in this case we want to be foldMap
:
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
since foldMap f
ends up being a generalized mconcat . map f
over Foldable
types instead of just lists, then a function to use as a "preprocessor" for hooking into the higher-order function being passed to ala'
(foldMap
), then in this case some Foldable t => t Node
to process. If you didn't want the preprocessing step you'd just use ala
, which uses id
for its preprocessor. Using this function can sometimes be difficult due to its complex type, but as the examples in the documentation show foldMap
is often a good choice.
The power of this is if you wanted to write your own newtype
wrapper for Maybe String
:
newtype FirstAsCaps = FirstAsCaps { getFirstAsCaps :: Maybe String }
firstAsCaps :: Maybe String -> FirstAsCaps
firstAsCaps = FirstAsCaps . fmap (fmap toUpper)
instance Monoid FirstAsCaps where
mempty = firstAsCaps Nothing
mappend (FirstAsCaps f) (FirstAsCaps g)
= FirstAsCaps $ ala First (uncurry . on (<>)) (f, g)
instance Newtype FirstAsCaps (Maybe String) where
pack = firstAsCaps
unpack = getFirstAsCaps
Then
> process' firstAsCaps input
Just "BAR"
If you're using Data.Monoid.First
, then this is just getFirst
. Many newtype wrappers use record syntax to provide an easy function to unwrap the newtype.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With