1

I wanted to make a type that I could use to verify if a tuple is homogenous. I wrote this, which should ensure that all elements of T are equal to X (Scastie):

type Homogenous[X] = [T <: Tuple] =>> T match {
  case EmptyTuple => DummyImplicit
  case X *: t => Homogenous[X][t] //Doesn't work
  case _ => Nothing
}

def f[T <: Tuple : Homogenous[String]](t: T) = ???

For whatever reason, the third line doesn't compile, with this error:

Homogenous[X] does not take type parameters

However, if I make Homogenous take 2 parameters, it compiles, but I can't use a context bound anymore (Scastie).

type Homogenous[X, T <: Tuple] = T match {
  case EmptyTuple => DummyImplicit
  case X *: t => Homogenous[X, t]
  case _ => Nothing
}

I don't understand why this happens. It's not as if the lambda [T] =>> is only being returned from a specific case of the match type, so the compiler should realize that Homogenous[X] always takes a parameter. Is this a bug or am I doing something wrong, and is there a fix/workaround?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
user
  • 7,435
  • 3
  • 14
  • 44
  • 1
    This is somewhat expected though. When you have a recursive function, you always have to declare it first, before calling it in the definition, like `val f = (a:Int) => (b:Int) => f(a)(b)` will not work because it does not know what f type is at the compile time, and it cannot do type deduction and definition at the same time. So you need to do `val f: Int=>Int=>Int = a => b => f(a)(b)` to make it work. But for a type defintion, dotty does not allow declaration(bound) and definition on the same line, so the compiler doesn't know Homogenous is a curried type constructor, thus the error you see. – SwiftMango Oct 16 '20 at 18:59
  • @texasbruce Good point. But I guess that's not the whole story because type `U[Int]` is allowed for `type U <: [T] =>> Any` but if we add upper bound in our case `type Homogenous[X] <: ([T <: Tuple] =>> Any) = [T <: Tuple] =>> T match { ...` still doesn't compile. I suspect the thing can also be how match types are translated into `Match` http://dotty.epfl.ch/docs/reference/new-types/match-types.html#representation-of-match-types – Dmytro Mitin Oct 16 '20 at 19:23
  • 1
    @texasbruce *"But for a type defintion, dotty does not allow declaration(bound) and definition on the same line"* For match types upper bound along with definition are allowed. See code snippet `type Concat[Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match {...` http://dotty.epfl.ch/docs/reference/new-types/match-types.html – Dmytro Mitin Oct 16 '20 at 19:29
  • @texasbruce It doesn't compile with the error "cannot combine bound and alias". Anyways, the two-parameter version compiled without a return type, so that can't be it – user Oct 16 '20 at 19:29
  • @DmytroMitin It's interesting the code from the sample works, but when you do `type Homogenous[X] <: ([T <: Tuple] =>> Any) = [T <: Tuple] =>> Any` even without match clause, it just gives error saying `cannot combine bound and alias`. Could be a bug? – SwiftMango Oct 16 '20 at 19:38
  • @texasbruce I don't know if it's a bug, maybe it's just that there's no point in putting a bound if you're using `=` anyways. Here's a [Scastie](https://scastie.scala-lang.org/WbggilvoQBilw0EsSHiExg) – user Oct 16 '20 at 19:46
  • @user That's a different case though. In the two parameter case, the compiler already knows Homogenous can take two parameter and return some type. Even it doesnt know what type it returns, it doesnt affect the compilation of calling it with two parameters. In the case when it is curried, it needs to know the return type of partially applied type, because it needs to know it returns a higher order type to allow further "applying" it. If scala allows some syntax like `type H[X][Y]`, then it would work fine, but it doesnt right now – SwiftMango Oct 16 '20 at 19:46
  • @texasbruce Well, `type W <: Any = Int` is not allowed. I was talking about match types. Well, maybe `type Concat[Xs <: Tuple, +Ys <: Tuple] <: Tuple = Xs match {...` and `type Homogenous[X, T <: Tuple] = T match {` are match types but `type Homogenous[X] = [T <: Tuple] =>> T match {..` is not. – Dmytro Mitin Oct 16 '20 at 19:47
  • @texasbruce But the compiler should be able to tell that the type of `Homogenous` is `[X] =>> [T <: Tuple] =>> TypeToBeInferred`, right? – user Oct 16 '20 at 19:48
  • 1
    @DmytroMitin That's true. Looks like type bounds and defintion at the same time is allowed only when the rhs is not a higher order type with match clause. Rather strange requirement. – SwiftMango Oct 16 '20 at 19:50
  • 1
    @texasbruce Well, as I mentioned in my answer multiple lists of type parameters are underimplemented. Maybe `type Homogenous[X][T <: Tuple] = T match {...` or `type Homogenous = [X] =>> [T <: Tuple] =>> T match {...` will compile then. – Dmytro Mitin Oct 16 '20 at 19:57

1 Answers1

2

Looks like a bug or underimplemented feature.

As a workaround try

trait Hom[X] {
  type Rec[T <: Tuple] = T match {
    case EmptyTuple => DummyImplicit
    case X *: t => Rec[t] 
    case _ => Nothing
  }
}

type Homogenous[X] = [T <: Tuple] =>> Hom[X]#Rec[T]

Maybe connected with

https://contributors.scala-lang.org/t/multiple-type-parameter-lists-in-dotty-si-4719

https://github.com/scala/bug/issues/4719

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66