Как обеспечить однородность кортежа?

#scala #tuples #dotty #scala-3

#scala #Кортежи #dotty #scala-3

Вопрос:

По независящим от меня причинам мой метод получает входные данные в виде кортежа. Этот кортеж должен содержать только экземпляры Foo , т. Е. Он должен выглядеть как (Foo, Foo ... Foo) и не должен иметь String или Int внутри. Я хочу проверить это во время компиляции, а не создавать исключение во время выполнения. Как я могу этого добиться?

Ниже приведен код, который у меня есть в настоящее время, что неверно:

 def f(tupleOfFoos: Tuple): Tuple = {
  for (x <- tupleOfFoos) assert(x.isInstanceOf[Foo])
  mapTuple(tupleOfFoos, irrelevantFunction)
}
  

Я открыт для использования Shapeless или новых функций, представленных в Dotty / Scala 3.

Ответ №1:

В Scala 2 с помощью Shapeless вы можете сделать это (Scastie):

 def f[T <: Product, H <: HList](tupleOfFoos: T)(
  implicit gen: Generic.Aux[T, H], 
  hev: LiftAll[({type E[T] = Foo =:= T})#E, H]
) = tupleOfFoos
  

LiftAll гарантирует Foo =:= X , что для каждого X in есть экземпляр H , и gen гарантирует, что T и H не являются полностью несвязанными типами.


В Dotty вы можете добавить параметр доказательства с типом соответствия для этого:

 type Homogenous[H, T <: Tuple] = T match {
    case EmptyTuple => DummyImplicit
    case H *: t => Homogenous[H, t]
    case _ => Nothing
}
    
def f[T <: Tuple](tupleOfFoos: T)(using Homogenous[Foo, T]) = tupleOfFoos
  

Это позволит вам вызывать f((Foo(), Foo(), Foo())) , но нет f((1, 2, 3)) .

Homogenous это рекурсивный тип соответствия с базовым случаем EmptyTuple . Если кортеж пуст, то он не заполняется не- Foo s , поэтому тип становится DummyImplicit , который уже имеет неявное значение в области видимости. В противном случае мы проверяем, выглядит ли он как (H, ...) / H *: t , и в этом случае нам нужно проверить, действительна ли остальная часть кортежа ( t ) . Если это не соответствует второму случаю, мы знаем, что кортеж недействителен, и в этом случае результатом будет Nothing то, что здравомыслящие люди не делают неявных значений.

Если вы хотите использовать границы контекста, вы можете создать дополнительный тип curried (Scastie):

 type Homo2[H] = [T <: Tuple] =>> Homogenous[H, T]

def f[T <: Tuple : Homo2[Foo]](tupleOfFoos: T) = tupleOfFoos
  

К сожалению, я не смог заставить его работать с одним типом curried (Scastie):

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