I want my List’s type parameters to be a mixture of a String, List[String], List[List[String]]…and so on.
With dotty’s union types I could defined List[String | List[String] | List[List[String]]...]. But then I will not be able to represent up to an infinite depth.
The following code gives illegal cyclic reference error
But I get your point, maybe it works using a type lambda?
However, note that union-types do not work to overcome erasure.
For example List[Int] | List[String] would erase to just List so it won’t work, for this kind of things an ADT is the best solution.
Just an idea, haven’t thought it through, may be nonsense… If this kind of data structure is central to your project(s), you might consider re-imagining List for this specific structure, something like (Scala 2):
sealed trait Nested[+T]
case object NNil extends Nested[Nothing]
case class Base[+T](value: T, link: Nested[T]) extends Nested[T]
case class Rec[+T](nested: Nested[T], link: Nested[T]) extends Nested[T]
This way you could avoid having both a List cons and an ADT instance per node, and it’d probably be easier to implement functionality dor traversal, folding, etc. on top. But this certainly would be considerable initial overhead and, again, I’m not really sure this makes sense at all…
It wouldn’t surprise me if the answer is simple “no” – this sounds a bit problematic. But the one way I can think of it maybe working is with Match Types, which allow you to do non-trivial computation (including recursive computation) at compile time.
That said: is the shape of the objects known at compile time? That is, do you know at compile time that this specific val is a List[List[List[String]]], as opposed to merely a List[String]? If you only know that at runtime (which is more common for questions like this), then I believe the answer is a hard “no”, since Types are about compile-time knowledge, not runtime knowledge…