How to preserve the type information when iterate over a tuple?

I use 3.4.1, and read Tuples bring generic programming to Scala 3 | The Scala Programming Language, where the doc mentions tuples are capable of storing data of different types while preserving the type of each entry. So I attempt to create tuple dynamically. There is no problem to retain the type information if I construct the tuple with the old way. For instance,

val f1: Int => Double  = (v: Int) => v * 3.14
val f2: Int => String = (v: Int) => s"literal string value from Int: $v"
val f3: Option[Int] => Boolean = (opt: Option[Int]) => if(opt.isEmpty) true else false 

val mytuples = (f1, f2, f3) // mytuples: (Int => Double, Int => String, Option[Int] => Boolean) = ...
mytuples(0)(1) // this produces 3.14 

However, if I dynamically construct the tuple, then iterating over that tuples, those functions become Any type.

 val dyntuples = f1 *: f2 *: f3 *: EmptyTuple
 val myf1 = dyntuples.productIterator.toIterable.take(1).head // myf1: Any = ...

How can I retrain the type information if creating tuple dynamically?

1 Like

Use the head function of the tuple, it will have the appropriate type. Tuple3

Lists and iterators are not heterogeneous datatypes. Tuple itself is.

If you need to modify the members of the tuple, you’ll need to use map and a polymorphic function.

1 Like

Thanks for the advice!

I can access the element in tuple and execute them by

mytuples.head(3)
mytuples.tail(0)(4)
mytuples.tail(1)(8.0)

Apart from that, I have a further question. How do I iterate over that tuple without losing the type information? I try following methods, but 2 of those types are Any, while one requires to know all elements beforehand, which is impossible.

def exec(tuple: Tuple): Unit = {
    tuple(3) // the tuple is of type Any so it does not work
}
exec(mytuples.tail)

mytuples match {
    case f1 *: f2 *: f3 *: EmptyTuple =>   // this requires to know all elements held by the tuple beforehand, but mytuple is created dynamically. The example code only uses 3 tuples, but the real code will contain N tuples  
       f1(2)
       f2(5)
       f3(None)
}

def exec(tuple: Tuple): Unit = tuple match {
  case head *: tail =>
    println(head(3)) // the type of head is Any
    exec(tail)
  case EmptyTuple => println("empty!")
}
exec(mytuples)

Many thanks.

As I said, .map exists for tuple types now. It takes a polymorphic function (meaning, map doesn’t typically know the types in the tuple, but can perform transformation on them anyway).

Map probably works best with match types, but I haven’t played around with it much. Tuple as a well supported heterogenous datatype is still a kind of new concept here, so keep in mind you’re dealing with waters that not many have explored much.

1 Like

Thank you very much for the advice!

Are you trying to solve a specific meta-problem? Or just playing around?

I am exploring the possibility of Tuple. There I have a use case that I want to hold an arbitrary data or functions, and then operates accordingly later on. Therefore, I am checking if it’s possible to preserve information as possible as I can so that later all those information can be used instead of asking users explicitly to provide those information again.

When is “later”? In a subsequent function call (i.e. compile-time), on another module / library depending on this (i.e. still compile-time, but split), or in runtime?

1 Like

The user will provide those data or functions at the compiler time. The Tuple functions I am going to provide will act as library, and the user who provides those information will call the code I supplied. So the code may look like

import mylib._
val holder = Holder() // case class Holder(mytuple: Tuple) { def put[A](something: A): Holder = Holder(something *: mytuple) }
holder.put(fn1).put(dataA).put(fn2)... // user then starts placing data to the holder
// some other methods that then retrieve the data or functions stored in holder for further operations such as executing the function or manipulate the data 

But declaring the variable of Holder i.e. mytuple as Tuple type turning the data stored in mytuple variable into Any type, which lost the information required during program execution. I expect the retrieved data would still preserve its type, for instance, Int => Int. This is the part I am still scratching my head.

I suggest your Holder use a type parameter bounded by a Tuple so you can retain the type info (Holder[T <: Tuple](val t: T)). Your put method simply uses the Tuple append to add a new tuple element and returns a new Holder with the new tuple. The put must be polymorphic to accept any type.

In the case of iterating, use the polymorphic function as shown above. For more on this take a look at:

HTHs

1 Like

Thanks for the advice. Trying with

class Holder[T <: Tuple](val tuples: T) { 
    def put[A](f: A): Holder[T] = new Holder(f *: tuples) 
}

the compiler returns

Found:    A *: T
Required: T
where:    T is a type in class Holder with bounds <: Tuple

Though refactoring to the below would be working,

class Holder[A](val tuples: A *: Tuple) { 
    def put[B](v: B): Holder[B] = new Holder(f *: tuples) 
}

only the first element is recognized as its own type A. The rest elements are still type of Tuple.

val holder = new Holder(f1 *: EmptyTuple).put(f2).put(f3)
holder.tuples.drop(0).head // this would work and the code can be executed like holder.tuples.drop(0).head(3) if it's a function
// but the elements starting from the second one will fail
holder.tuples.drop(1).head // this throws error message "value head is not a member of Tuple"

Unfortunately to solve this you need more advanced Scala 3 functionality.

The first problem is that your put returns the same type as the input. However, the new tuple has a different type. In your second attempt you just provide the type of the first element and declare the rest of the tuple as a generic Tuple.

In the link below I provide a solution. Hover over the values to see the types. You will see that I use a transparent inline function. As a side note, I find working with tuples requires some advanced coding for anything more complicated that simple data “transfer”. If you are a beginner I suggest considering another solution.

HTHs

1 Like

Uhm you don’t need anything too fancy.
Just properly type the return type:

final class Holder[T <: Tuple](val data: T): 
  def put[A](v: A): Holder[A *: T] =
    new Holder(v *: data)

You can see it working in this Scastie: Scastie - An interactive playground for Scala.

Now, I do agree that just holding the tuple is the easy part. Especially because this Holder is not doing anything useful at all, is just a proxy to Tuple.
The hard part is using / manipulating the Holder / Tuple later.

3 Likes

Bhaaa… why do I always complicate stuff?

1 Like

Oh, thank you. This really solves my problem!

Thanks! I have some Scala experience. Though it’s difficult, it’s interesting because I have not heard of generic programming in the past. That opens the a new way to me how the code can be abstracted.

1 Like