Scala Type-Inference для конструктора типов

У меня есть вопрос относительно вывода типов в конструкторах типов Scala. Я использую Scala 2.9.1...

Предположим, я определил дерево:

 sealed trait Tree[C[_], A]
 case class Leaf[C[_], A](a: A) extends Tree[C, A]
 case class Node[C[_], A](a: A, c: C[Tree[C, A]]) extends Tree[C, A]

И определил BinaryTree на основе моего определения дерева:

 type Pair[A] = (A, A)
 type BinaryTree[A] = Tree[Pair, A]

Теперь я могу определить BinaryTree целых чисел как таковой:

 val tree: BinaryTree[Int] = Node[Pair, Int](1, (Leaf(2), Leaf(3)))

Проблема в том, что я должен предоставлять параметры типа всякий раз, когда я создаю экземпляр Node.

Итак, если сделать это:

 val tree: BinaryTree[Int] = Node(1, (Leaf(2), Leaf(3)))

Я получаю сообщение об ошибке:

error: no type parameters for method apply: (a: A, c: C[Tree[C,A]])Node[C,A] in 
object Node exist so that it can be applied to arguments (Int, (Leaf[Pair,Int], Leaf[Pair,Int]))
 --- because ---
 argument expression's type is not compatible with formal parameter type;
 found   : (Leaf[Pair,Int], Leaf[Pair,Int])
 required: ?C[Tree[?C,?A]]
   val tree: BinaryTree[Int] = Node(1, (Leaf(2), Leaf(3)))
                               ^

Можно ли каким-либо образом заставить средство проверки типов не указывать типы Node в явном виде?

Спасибо!



Пересмотрено после комментариев Дидьеда

Если я правильно понимаю, утверждение

 type Pair[A] = (A, A)

в моем первоначальном вопросе не работает, поскольку это объявление Pair является просто синтаксическим сахаром для конструктора типа Tuple2 (для которого требуются два параметра типа). Это приводит к сбою механизма вывода типов.

Если я объявлю свой собственный класс Pair (как предлагает Дидье в своем ответе), мне удастся заставить дерево работать правильно.

// Assume same Tree/Leaf/Node definition given above
case class MyPair[A](_1: A, _2: A)
type BinaryTree[A] = Tree[MyPair, A]

Тогда я смогу сделать это...

scala> val t: BinaryTree[Int] = Leaf(3)
t: BinaryTree[Int] = Leaf(3)

scala> val t2: BinaryTree[Int] = Node(1, MyPair(Leaf(2), Leaf(3)))
t2: BinaryTree[Int] = Node(1,MyPair(Leaf(2),Leaf(3)))

Я знаю, что Didierd мимоходом упомянул это решение, но, похоже, оно ведет себя так, как я хочу. Пожалуйста, дайте мне знать, что вы думаете!


person shj    schedule 03.12.2011    source источник
comment
Что касается вашего исправленного кода: он больше похож на решение @David. Типы не выводятся полностью, вы должны ввести выражение явно как BinaryTree[Int]. Я думаю, это вполне разумно. Ковариантное решение позволяет избежать этого, но за него приходится платить: хотя ковариантность в основном полезна, она ограничивает возможности класса. В частности, он работает только с ковариантными типами C.   -  person Didier Dupont    schedule 05.12.2011


Ответы (2)


Существует проблема, с которой стоит начать вывод C[X] = (X,X). Предположим, вы передаете (String, String) где-то, где компилятор ожидает C[String] и должен вывести C, C[X] может быть (X, X), (X, String), (String, X) или даже (String, String) с X фантомом.

Объявление псевдонима Pair не помогает. Я считаю, что вам придется объявить case class Pair[A](_1: A, _2: A) разрешенным, вывод C[X] = Pair[String] и X фантом все еще возможен, но, к счастью, компилятор этого не делает.

Тем не менее, когда вы пишете Tree(1, Pair(Leaf(2), Leaf(3)), это не будет означать C в листьях. Я не очень уверен, почему. Но в любом случае, он никак не мог вывести это, когда вы просто пишете val l = Leaf(2).

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

sealed trait Tree[+C[+X], +A]
case class Leaf[+A](a: A) extends Tree[Nothing, A]
case class Node[+C[+X], +A](a: A, c: C[Tree[C,A]]) extends Tree[C,A]

с ковариацией вы удаляете C из листа, поэтому его не нужно выводить

case class Pair[+A](left: A, right: A)

val tree = Node(1, Pair(Leaf(2), Node(3, Pair(Leaf(3), Leaf(4)))))
tree: Node[Pair,Int] = Node(1,Pair(Leaf(2),Node(3,Pair(Leaf(3),Leaf(4)))))

Побочное замечание, не было бы больше смысла иметь

case object Empty extends Tree[Nothing, Nothing]

а не Leaf. С Leaf есть формы двоичного дерева, которые вы не можете получить.


Обновление относительно ваших комментариев:

Во-первых, не стоит слишком заморачиваться с фантомным типом, я не должен был упоминать об этом. Если вы определяете тип T[X], а X не появляется иначе в определении T, он называется фантомным типом. С этим можно написать умный код, чтобы гарантировать, что некоторые свойства будут проверены во время компиляции, но это не главное.

На самом деле, когда компилятору scala задаются некоторые типы T и X, и он должен вывести C, так что C[X] является (супертипом) T - в моем примере это было T = (String, String) и X = String — это будет работать, только если T (или супертип) является параметризацией типа ровно с одним параметром типа. В более общем смысле, столько параметров типа, сколько имеет параметр типа C. Поскольку C имеет один, а Tuple2 - два (вы определили псевдоним Pair не в счет), он не может работать.

Я пытался указать, что без такого правила у компилятора будет много вариантов для C. Если бы я знал, что (String, Int) — это C[String], и что я должен угадать, что такое C, то я бы подумал, что C[X] — это (X, Int). Когда вы пишете если передано (String, Int), не будет, если выводить (Any, Any), это не имеет смысла, учитывая, что он пытается вывести конструктор типа. Ответ должен быть C[X] = something with X (за исключением возможности того, что X является фантомом). Совершенно другое было бы иметь Pair[X] = (String, Int) и выводить X. Тогда действительно получится X = Any. Итак, если C[String] = (String, String), C[X] = (X, String) является таким же решением, как и C[X] = (X,X).

Что касается вашего второго комментария относительно List, это та же проблема, которая существует и с Pair после того, как вы определили case class Pair (третий абзац в ответе выше), а именно то, что он не будет делать вывод, что такое C, когда вы пишете Leaf(2), где C не появляется . Именно здесь вступает в действие ковариация, избавляющая от параметра C в Leaf и, следовательно, от необходимости его выводить.

person Didier Dupont    schedule 03.12.2011
comment
Меня немного смущает первый абзац. Что означает, что X является фантомом? Если я передам (String, String) где-нибудь, компилятор ожидает C[String], разве он не выведет это как (String, String)? Если бы я передал (String, Int), не будет ли это означать (Any, Any)? - person shj; 04.12.2011
comment
Кроме того, я получаю аналогичную ошибку, если я использую List вместо своего псевдонима Pair. Поскольку списки однородны, разве он не может определить тип? Большое спасибо за Вашу помощь! - person shj; 04.12.2011
comment
Обновлено, чтобы ответить на ваши комментарии. - person Didier Dupont; 04.12.2011

Единственный вариант, который пришел мне в голову, заключался в том, чтобы вместо этого аннотировать аргумент Pair. У других людей, я уверен, будут другие идеи.

object BinaryTreeTest {
  sealed trait Tree[C[_], A]
  case class Leaf[C[_], A](a: A) extends Tree[C, A]
  case class Node[C[_], A](a: A, c: C[Tree[C, A]]) extends Tree[C, A]

  type Pair[A] = (A, A)
  type BinaryTree[A] = Tree[Pair, A]

  val p: Pair[Tree[Pair, Int]] = (Leaf(2), Leaf(3))    
  val t: BinaryTree[Int] = Node(1, p)    
}
person Dave L.    schedule 03.12.2011