4
votes

Is it possible to pattern match tuples in Haskell, but without knowing the dimension of tuple? I want to create a function which mach against any tuple, which first element is A, like:

data A = A Int
test args@(A a,..) = a

I know there is Data.Tuple.Select module and I can use it like this:

test args = case sel1 args of
    A a -> a
    ...

But is this the only way of doing this or has Haskell got some default mechanisms to match against any dimension tuple?

3

3 Answers

7
votes

You could use the ViewPatterns extension to pattern match the result of a function applied to an argument:

{-# LANGUAGE ViewPatterns #-}

data A = A Int
test (fst -> A a) = a

You could use lenses to project out arbitrary fields:

{-# LANGUAGE ViewPatterns #-}

import Control.Lens
import Control.Arrow ((&&&))

data A = A Int
test (fields _1 _3 -> (A x, A y)) = x + y

fields f1 f2 = (^.f1) &&& (^.f2)

-- > test (A 1, A 2, A 3)
-- > 4
4
votes

If you don't want to use type classes, you could also use nested tuples. So instead of having a tuple of type (A, B, C, D) you have a tuple of (A, (B, (C, D))).

You could then easily match against the first element of a however deeply nested tuple, like this:

test :: (A, b) -> Int
test (A a, _) = a
2
votes

Any solution will have to somehow generalize over tuples as by default they're simply disjoint types. The most common solution will use typeclasses to index on the idea of types which "have a first element" such as what Control.Lens or Data.Tuple.Select does.

class Sel1 a b | a -> b where sel1 :: a -> b
instance Sel1 (a1,a2) a1 where sel1 (x,_) = x
instance Sel1 (a1,a2,a3) a1 where sel1 (x,_,_) = x
instance Sel1 (a1,a2,a3,a4) a1 where sel1 (x,_,_,_) = x
...

or

instance Field1 (Identity a) (Identity b) a b where
  _1 f (Identity a) = Identity <$> indexed f (0 :: Int) a
instance Field1 (a,b) (a',b) a a' where
  _1 k ~(a,b) = indexed k (0 :: Int) a <&> \a' -> (a',b)
instance Field1 (a,b,c) (a',b,c) a a' where
  _1 k ~(a,b,c) = indexed k (0 :: Int) a <&> \a' -> (a',b,c)
instance Field1 (a,b,c,d) (a',b,c,d) a a' where
  _1 k ~(a,b,c,d) = indexed k (0 :: Int) a <&> \a' -> (a',b,c,d)
...

In both cases, consider the type of your function, it'll have to have a way to specify your first argument is a "kind of thing with a first element".

test :: (Sel1 s A)       => s -> ...
test :: (Field1 s t A b) => s -> ...

You can also go the route of fixed-vector and consider tuples as short homogenous vectors. You lose the ability to act on heterogenous vectors, but you gain neat types (but ugly values) like

test :: (Vector v A, Index N1 (Dim v)) => v A -> ...
test v = let (A a) = index (1,2) (undefined :: Z) in ...

though for all its magic it still achieves this work through type classes.