Любое решение будет каким-то образом обобщать над кортежи, как по умолчанию они просто непересекающиеся типы. Наиболее распространенное решение будет использовать typeclasses для индексации по идее типов, которые «имеют первый элемент», например, Control.Lens
или Data.Tuple.Select
.
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
...
или
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)
...
В обоих случаях учитывать тип вашей функции, он будет иметь возможность указать свой первый аргумент является «вид вещи с первым элементом» ,
test :: (Sel1 s A) => s -> ...
test :: (Field1 s t A b) => s -> ...
Вы также можете идти по пути fixed-vector
и рассмотрим кортежей в виде коротких однородных векторов. Вы теряете способность действовать на гетерогенных векторах, но вы получаете аккуратные тип (но уродливые значения), как
test :: (Vector v A, Index N1 (Dim v)) => v A -> ...
test v = let (A a) = index (1,2) (undefined :: Z) in ...
хотя при всей своей магии она все еще достигает эту работу через классы типа.
Спасибо за это решение! Я буду использовать его. Это не отвечает на мой вопрос (потому что он использует вложенные кортежи), но это очень, очень здорово. –