Проблема компиляции в Scala с F-ограниченными типами и экзистенциальными типами

Я использую F-ограниченный тип для того, чтобы иметь возможность вернуть текущий тип

trait Board[T <: Board[T]] {
  def updated : T
}

И я пытаюсь написать универсальный вспомогательный метод, который использует его.

Вопрос в следующем: почему следующее не компилируется?

object BoardOps {
  def updated(board: Board[_]) = {
    board.updated.updated
  }
}

Ошибка value updated is not a member of _$1

Я понял эти 2 обходных пути. Они эквивалентны?

object BoardOps {
  def updated(board: Board[_<:Board[_]]) = {
    board.updated.updated
  }
}

object BoardOps {
  def updated[T <: Board[T]](board: T) : T = {
    board.updated.updated
  }
}

2 ответа

Решение

почему следующее не компилируется?

С помощью Board[_] как тип параметра говорит компилятору "мне все равно, какой тип параметра внутри платы". А именно, это, для компилятора, является экзистенциальным типом, он не знает никаких особенностей об этом типе. В качестве таких, board.updated возвращает "невыразимый" или непрозрачный тип, потому что мы сказали компилятору "выбросить" эту информацию о типе.

Я понял эти 2 обходных пути. Они эквивалентны?

Ваш предыдущий пример использует экзистенциальный тип с ограничением, чтобы быть подтипом Board[_]или более формально пишем:

Board[T] forSome { type T <: Board[U] }

Где на самом деле называет компилятор T -> $_1 а также U -> $_2

Опять же, мы ничего не знаем о параметре внутреннего типа, только о том, что он имеет верхнюю границу Board[_], В последнем примере используется универсально определенный тип с именем T, который компилятор может использовать для определения типа возвращаемого значения метода определенного типа T скорее, чем Any,

Чтобы ответить на ваш вопрос, нет, они не эквивалентны. Экзистенциальные и универсально выраженные типы двойственны друг другу. Дополнительную информацию об экзистенциалах можно найти в разделе Что такое экзистенциальный тип? и https://www.drmaciver.com/2008/03/existential-types-in-scala/

Не компилируется, потому что как только пишешь Board[_], компилятор не делает ничего полезного о параметре анонимного типа _,

Есть несколько обходных путей (которые не совпадают с предложенными вами):

  1. использование Board[X] forSome { type X <: Board[X] }
  2. Используйте сопоставление с образцом, чтобы получить больше информации о типе

С помощью forSome экзистенциальная количественная оценка

Это можно легко исправить с помощью forSomeэкзистенциальное количественное определение:

import scala.language.existentials

object BoardOps_forSome {
  def updated(board: Board[X] forSome { type X <: Board[X] }) = {
    board.updated.updated.updated
  }
}

Это позволяет вам вызывать updated неограниченное количество раз.


Использование сопоставления с образцом

Вы можете обойти это без изменения сигнатуры, используя сопоставление с образцом. Например, эта безбожная конструкция позволяет применять метод updated три раза (работает неограниченное количество раз):

object BoardOps_patternMatch {
  def updated(board: Board[_]) = {
    board match {
      case b: Board[x] => b.updated match {
        case c: Board[y] => c.updated match {
          case d: Board[z] => d.updated
        }
      }
    }
  }
}

Это потому, что как только вы связываете неизвестный тип с типом переменных x, y, zкомпилятор вынужден выполнять дополнительную работу с логическим выводом и делает вывод, что x <: Board[_$?] и т. д. К сожалению, он делает вывод только один шаг за раз, потому что, если он попытается вычислить наиболее точный тип, вычисление типа будет расходиться.


Верхние границы и универсальное количественное определение не совпадают

Обратите внимание, что ваш первый обходной путь работает только дважды:

object BoardOps_upperBound_once {
  def updated(board: Board[_<:Board[_]]) = {
    board.updated.updated // third .updated would not work
  }
}

Следовательно, он не эквивалентен вашему второму обходному пути, который также работает неограниченное количество раз, например, с тремя вызовами updated:

object BoardOps_genericT {
  def updated[T <: Board[T]](board: T) : T = {
    board.updated.updated.updated
  }
}
Другие вопросы по тегам