Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern matching vs. constructors

Tags:

haskell

I have two almost equal functions for different data constructors and would like to know if there is a possibility to unify both. A minimalistic example would be the following

f_maybe :: Maybe a -> a -> a
f_maybe (Just x) _ = x
f_maybe _        x = x

and

data T a = T1 a | T2 Int | T3

f_t :: T a -> a -> a
f_t  (T1 x) _ = x
f_t  _      x = x

Is the a way to define only one function parametrized by the type constructors (Maybe or T) and the data constructor (Just or T1) doing both?

like image 780
dmw64 Avatar asked Dec 14 '22 07:12

dmw64


2 Answers

Once you implemented Foldable instance for your data type, this idea can be expressed as foldr const.

data T a = T1 a | T2 Int | T3

instance Foldable T where
    -- if it's T1, it has a value
    foldMap f (T1 a) = f a
    -- if it's T2 or T3, it's considered "empty"
    foldMap f (T2 _) = mempty
    foldMap f T3     = mempty

You can even let generic-deriving package derive the above Foldable instance for you:

{-# LANGUAGE DeriveGeneric #-}

import           Generics.Deriving.Foldable
import           GHC.Generics

data T a = T1 a | T2 Int | T3
    deriving (Generic1)

instance Foldable T where
    foldMap = gfoldMapdefault

(In your case the instance is not ambiguous since only one of the constructors may contain exactly one value of type a. if the data type gets over-complicated with multiple constructors containing one or more a, you'd better do it manually or check carefully what that package derived)

And some results from ghci:

-- `Maybe` already has the instance defined by the standard library
> foldr const 2 (Just 3)
< 3
> foldr const 2 Nothing
< 2

> foldr const 2 (T1 3)
< 3
> foldr const 2 (T2 10)
< 2
> foldr const 2 T3
< 2
like image 118
zakyggaps Avatar answered Dec 22 '22 17:12

zakyggaps


You can write a unified function that has any behavior you want and works on any type of argument, but you cannot pattern match without concrete data constructor (polymorphicly), AFAIK.


Ask yourself what type signature of this unified function would look like? The type signature represents idea behind function. What do Maybe and T have in common in this case so that you what to unify functions that work on them? What kind of abstraction are you trying to build?

All these things are not clear from your question. If your idea is to just cut boilerplate (and this is not exactly boilerplate), then do not try to unify things that are different just to make code shorter, you will get something confusing in the end.

Types actually should be different and functions should reject arguments of wrong type, not work on anything that looks remotely similar. Too polymorphic code is often not a good idea, because wrong code may happen to have some meaning for type checker and then you will get program that compiles, but does wrong things.

like image 21
Mark Karpov Avatar answered Dec 22 '22 16:12

Mark Karpov