1
votes

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.

2
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). Why don't you just define your own type instead of (ab-)using tuples?flawr
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
@bluescorpion If you use the record syntax, it's super easy to extend.4castle
have a look at answer with lens package stackoverflow.com/a/23860744/187261Nazarii Bardiuk

2 Answers

2
votes

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.

1
votes

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) -> ???