1

Is there a way to write the following function in Haskell:

get_nth_element :: Int->(???)->(???)
get_nth_element n tpl = ...

I'm interested in a solution for tuples, not lists, for which the solution is trivial. The reason being that tuples can aggregate different types, of course.

The intermediate goal is to be able to write a function that given a tuple and a value it returns a tuple that adds the given value to the given tuple. The ultimate goal is to write a generic Cartesian product function that, given a tuple of n (potentially different types of) lists, returns the Cartesian product list of all the resulting n-dimensional tuples (using, say, applicative operators <$>, <*>).

This can be achieved in C++ (using variadic templates and SFINAE), so I assume there should be a way to do it in Haskell.

[Update] C++ (11 and above) has std::get<i>(tuple) and std::tuple_cat. For example this compiles and runs just as expected:

#include <iostream>
#include <tuple>

template<typename T, typename ...Args>
std::tuple<T,Args...> grow_tuple(T v, std::tuple<Args...> t)
{
  return std::tuple_cat(std::make_tuple(v), t);
}

int main(void)
{
  std::tuple<int, std::string> t1{1, "Howdy!"};

  int i = 13;
  auto t2 = grow_tuple(i, t1);

  std::cout<<"("<<std::get<0>(t2)
       <<","<<std::get<1>(t2)
       <<","<<std::get<2>(t2)<<")\n";

  std::cout<<"Done!"<<std::endl;
}

Essentially, a mechanism is needed here to handle a variadic list of types. I can hardly believe Haskell does not have that.

[Update] Note: this is not about accessing an a-priori known element from a tuple of a-priori known length. It's about accessing an element given by a variable index in an variable tuple (of length and type unknown a-priori). The former implies a fixed definition of a lambda accessor ((_,_,x,_,_) -> x) where both position of interest in tuple and tuple length are known a-priori. While my question does not make any such a-priori known assumptions. The function signature I am looking for does not take such lambda accessor, but an integer argument for the positional index inside the tuple and a generic tuple argument (generic meaning of unknown length and contained types). Hence this question is different than Haskell - Accessing a Specific Element in a Tuple.

blue scorpion
  • 387
  • 1
  • 10
  • I think you'd get really messy code for getting the `nth` element, and you'd probably need some custom types or typeclasses to do that. (see for example [here](http://hackage.haskell.org/package/tuple-0.3.0.2/docs/Data-Tuple-Select.html)). Why don't you just define your own type instead of (ab-)using tuples? – flawr Mar 02 '18 at 20:55
  • Even if I define my own type, it must be capable to grow with more types (akin to variadic templates in C++). So, in essence an extensible tuple type. Again, the goal for this is being generic. I don't want to limit to a predefined number of elements in a tuple or predefined types. – blue scorpion Mar 02 '18 at 21:07
  • @bluescorpion If you use the record syntax, it's super easy to extend. – 4castle Mar 02 '18 at 22:07
  • Possible duplicate of [Haskell - Accessing a Specific Element in a Tuple](https://stackoverflow.com/questions/5844347/haskell-accessing-a-specific-element-in-a-tuple) – Nazarii Bardiuk Mar 03 '18 at 00:45
  • have a look at answer with lens package https://stackoverflow.com/a/23860744/187261 – Nazarii Bardiuk Mar 03 '18 at 00:47
  • @ Nazarii Bardiuk: This is different than https://stackoverflow.com/questions/5844347/haskell-accessing-a-specific-element-in-a-tuple. This is not about accessing an a-priori known element from a tuple of a-priori known length. It's about accessing an element given by a variable index in an variable tuple (of length and type unknown a-priori). The former implies a fixed definition of a lambda accessor `((_,_,x,_,_) -> x)` where both position of interest in tuple and tuple length are known a-priori. While my question does not make any such a-priori known assumptions. – blue scorpion Mar 03 '18 at 02:55

2 Answers2

2

The tried-and-true HList seems to match your use case.

{-# LANGUAGE DataKinds, ExplicitForAll, GADTs, PolyKinds,
             TypeFamilies, TypeOperators, UndecidableInstances
#-}
import GHC.TypeLits(TypeError(..), ErrorMessage(..))

-- HList takes a list of types and creates a type that contains one value of each
data HList (ts :: [*]) where
  (:^:) :: t -> !(HList ts) -> HList (t:ts)
  -- strictness removes extra bottoms compared to tuples
  HNil  :: HList '[]
infixr 5 :^:

-- natural numbers are either zero or the successor of another
data Nat = Zero | Succ Nat
-- singletons
data SNat (n :: Nat) where
  SZero :: SNat Zero
  SSucc :: SNat n -> SNat (Succ n)

-- index into a normal list
type family IndexL (i :: Nat) (xs :: [k]) :: k where
  IndexL Zero (x:xs) = x
  IndexL (Succ n) (x:xs) = IndexL n xs
  IndexL i xs = TypeError (Text "Cannot index "
                      :<>: ShowType i
                      :<>: Text " into the list "
                      :<>: ShowType xs
                          )

-- index into an HList
index :: forall (i :: Nat) xs. SNat i -> HList xs -> IndexL i xs
index SZero (x :^: _) = x
index (SSucc n) (_ :^: xs) = index n xs
-- despite appearances, this function is total
-- you'll get a compiler error if you try to add an HNil case.

There are a bunch of instances (e.g. Read, Eq) to write/derive to make this somewhat useable.

main = do let test = 1 :^: "abc" :^: (7 :^: Zero :^: HNil) :^: HNil
          print $ index SZero test + 2
          print $ ord <$> index (SSucc$SZero) test
          print $ index SZero $ index (SSucc$SSucc$SZero) test

Inserting elements involves the addition of a type family Inject (x :: k) (i :: Nat) (xs :: [k]) :: [k] where ... on the type level and corresponding function on the values. Writing a function of type Applicative f => HList [f a, f b, ...] -> f (HList [a, b, ...]) is harder (in the sense of more typing, not conceptually), but doable.

You don't get the nice numeric literals, but that's really just "meh". You can use TH to rectify that.

HTNW
  • 27,182
  • 1
  • 32
  • 60
1

It is impossible to implement in general because each element in tuple has different type

Take for example tuple of 3 items. It is impossible to determine type of output in compile time

 nth :: (a,b,c) => Int -> (a,b,c) -> ???
Nazarii Bardiuk
  • 4,272
  • 1
  • 19
  • 22