Является ли PartialFunction или Else более свободными в границах своего типа, чем должно быть?

Давайте определим PartialFunction[String, String] и PartialFunction[Any, String]

Теперь, учитывая определение orElse

def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] 

Я ожидаю, что не смогу составить два, так как

A String
A1 Any

и поэтому граница A1 <: A (т.е. Any <: String) не выполняется.

Неожиданно я могу составить их и получить PartialFunction[String, String], определенный для всего домена String. Вот пример:

val a: PartialFunction[String, String] = { case "someString" => "some other string" }
// a: PartialFunction[String,String] = <function1>

val b: PartialFunction[Any, String] = { case _ => "default" }
// b: PartialFunction[Any,String] = <function1>

val c = a orElse b
// c: PartialFunction[String,String] = <function1>

c("someString")
// res4: String = some other string

c("foo")
// res5: String = default

c(42)
// error: type mismatch;
//   found   : Int(42)
//   required: String

Более того, если я явно укажу параметры типа orElse

a orElse[Any, String] b
// error: type arguments [Any,String] do not conform to method orElse's type parameter bounds [A1 <: String,B1 >: String]

компилятор, наконец, показывает какой-то смысл.

Есть ли какое-то колдовство системы типов, которое я упускаю из виду, из-за которого b является допустимым аргументом для orElse? Другими словами, почему A1 выводится как String?

Если компилятор выводит A1 из b, то это должно быть Any, так где же еще начинается цепочка вывода, ведущая к String?


Обновлять

Поиграв с REPL, я заметил, что orElse возвращает тип пересечения A with A1, когда типы не совпадают. Пример:

val a: PartialFunction[String, String] = { case "someString" => "some other string" }
// a: PartialFunction[String,String] = <function1>

val b: PartialFunction[Int, Int] = { case 42 => 32 }
// b: PartialFunction[Int,Int] = <function1>

a orElse b
// res0: PartialFunction[String with Int, Any] = <function1>

Начиная с (String with Int) <:< String это работает, хотя результирующая функция практически непригодна для использования. Я также подозреваю, что String with Any объединяется в Any, учитывая, что

import reflect.runtime.universe._
// import reflect.runtime.universe._   

typeOf[String] <:< typeOf[String with Any]
// res1: Boolean = true

typeOf[String with Any] <:< typeOf[String]
// res2: Boolean = true

Вот почему при смешивании String и Any получается String.

При этом, что происходит под капотом? По какой логике объединяются несовпадающие типы?

Обновление 2

Я свел вопрос к более общей форме:

class Foo[-A] {
  def foo[B <: A](f: Foo[B]): Foo[B] = f
}

val a = new Foo[Any]
val b = new Foo[String]

a.foo(b) // Foo[String] Ok, String <:< Any
b.foo(a) // Foo[String] Shouldn't compile! Any <:!< String
b.foo[Any](a) // error: type arguments [Any] do not conform to method foo's type parameter bounds [A <: String]

person Gabriele Petronella    schedule 19.08.2014    source источник
comment
Почти уверен, что компилятор просто выводит самый строгий тип возвращаемого значения, который он может найти.   -  person Falmarri    schedule 20.08.2014
comment
а чем выводить другие типы в обратном порядке?   -  person Gabriele Petronella    schedule 20.08.2014
comment
Nitpick: String с Int не является типом объединения, поскольку по определению это требует, чтобы и String, и Int независимо выполняли интерфейс String с Int, что не так. Строка не является ‹: Строка с Int и Int не является ‹: Строка с Int.   -  person Utaal    schedule 20.08.2014
comment
спасибо @Utaal, это действительно пересечение, а не союз. Починил это!   -  person Gabriele Petronella    schedule 20.08.2014


Ответы (4)


Вы получаете это с ног на голову.

Вы всегда можете передать методу, требующему параметр типа A, любой аргумент типа B <: A, то есть любой подтип A. Это если у вас

def foo(a: Animal)

вы можете передать Dog в foo, потому что Dog <: Animal.

Таким же образом, если у вас есть

def foo(l: List[Animal])

вы можете передать ему List[Dog], потому что List является ковариантным с его параметром типа и так как Dog <: Animal, то List[Dog] <: List[Animal]

Теперь, если у вас есть

def foo(pf: PartialFunction[String, String])

вы можете передать PartialFunction[Any, String], потому что PartialFunction контравариантно первому параметру типа и ковариантно второму. Так как Any >: String, то PartialFuncion[Any, String] <: PartialFunction[String, String].

Теперь для границ типа компилятор попытается вывести A1 и B1, так что

  • A1 является подтипом A
  • B2 является подтипом B

Для этого он будет искать:

  • самый большой общий подтип Any и String, поскольку A и A1 находятся в контравариантном положении
  • наименее распространенный супертип String и String, поскольку B и B1 являются ковариантными позициями

Результаты

  • A1 String
  • B1 String

Случай, в котором вы составляете PartialFunction[String, String] с PartialFunction[Int, Int], является странным случаем предыдущего примера, в котором:

  • наибольшим общим подтипом String и Int является String with Int, т. е. пересечение двух типов, которое является подтипом обоих (в данном случае это почти то же самое, что сказать Nothing: быть оба String и Int не маловероятно)
  • наименее распространенным супертипом String и Int является Any

следовательно

val a: PartialFunction[String, String] = ...
val b: PartialFunction[Int, Int] = ...
a orElse b // PartialFunction[String with Int, Any] // as expected, although not very useful...
person Gabriele Petronella    schedule 20.08.2014
comment
Вы получаете это с ног на голову. Вы сами себе во втором лице отвечаете? : Д. Независимо от того, что, кажется, дает хороший обзор того, что происходит. - person Kigyo; 20.08.2014
comment
Хороший ответ! Гораздо более полный, чем разум, хотя я не считаю абсолютно необходимым повторно объяснять здесь ковариантность и контравариантность, заинтересованные стороны могут найти это в других местах. - person jedesah; 20.08.2014
comment
Тем не менее, все еще не полностью понимаю часть String with Int. Возможно, я просто не уделил достаточно времени размышлениям об этом, но если вы действительно хотите сделать свой ответ исчерпывающим, вы можете немного подробнее остановиться на этом. - person jedesah; 20.08.2014
comment
@UndercoverAgent Представьте себе: class A extends B with C. Тогда для val a = new A верно все следующее. a.isInstanceOf[B], a.isInstanceOf[C] и a.isInstanceOf[B with C]. Вы используете with все время с наследованием. Он объединяет типы с and. Это В и С одновременно. - person Kigyo; 20.08.2014
comment
@Kigyo, именно, это я отвечаю на свой наивный вопрос в ТАКОМ стиле! Что касается объяснения контравариантности, я объяснял это ОП, который, видимо, забыл, что это такое: P - person Gabriele Petronella; 20.08.2014

Я собирался сказать, что PartialFunction[Any, String] является подтипом PartialFunction[String, String] из-за контравариантности, если я правильно понимаю. Это объяснило бы поведение, описанное в вашем вопросе до обновления, но вы меня запутали с этим типом союза.

Я даже не знаю, что, черт возьми, означает String with Int!

person jedesah    schedule 20.08.2014
comment
Это не вопрос контравариантности, привязка типа очень специфична: A1 (т.е. Any) должен быть подтипом A (т.е. Any), что неверно. Дисперсия здесь не при чем. - person Gabriele Petronella; 20.08.2014
comment
@GabrielePetronella A1 ‹: A или String ‹: String. PF[Any, String] ‹: PF[String, String]. - person som-snytt; 20.08.2014
comment
Я удалил свой отрицательный голос, так как я был неправ. Однако этот ответ определенно потребует некоторой проработки. - person Gabriele Petronella; 20.08.2014
comment
Это подтип, но не подкласс. - person Alexey Romanov; 20.08.2014

Это, конечно, расплывчато и только мое скромное мнение. Предложения и комментарии приветствуются.

Взяв из этого ТАК вопрос. (Как узнать, объект является экземпляром типа TypeTag?)

import scala.reflect.runtime.universe._
implicit class MyInstanceOf[U: TypeTag](that: U) {
  def myIsInstanceOf[T: TypeTag] = 
    typeOf[U] <:< typeOf[T]
}

У нас есть способ проверить isInstanceOf без стирания.

val b: PartialFunction[Any, String] = { case _ => "default" }
b.myIsInstanceOf[PartialFunction[String, String]] //true

И это только имеет смысл. Если у вас есть функция из Any => String, то она принимает любые входные данные. Таким образом, он также принимает ввод String. Вот почему его также можно рассматривать как функцию из String => String. В основном это можно рассматривать как T => String для любого T.

Итак, в конце концов компилятор соглашается на A -> String и A1 -> String.

 a.orElse[String,String](b) //works

Изменить: Заключительные мысли

Вы не должны думать о A1 <: A как об ограничении. Он только выведет тип результирующего PartialFunction. Невозможно применить orElse. Оба задействованных PF являются контравариантными по A, поэтому всегда можно найти общий подтип для ОБОИХ, который удовлетворяет A1 <: A.

Я думаю аналогией будет сложение дробей, где вы думаете, ой, они не имеют общего знаменателя и поэтому не могут быть сложены. Обе дроби можно скорректировать (или: увидеть по-разному), чтобы они имели общий знаменатель. Компилятор, однако, хочет найти наименьший общий знаменатель, а не прибегать к простому пути с умножением на другой знаменатель. (A with A')

Для другого типа B то же самое. Оба являются ковариантными, поэтому всегда можно найти общий супертип. Any в худшем случае.

person Kigyo    schedule 20.08.2014
comment
В целом я тоже согласен с компилятором. Однако я не понимаю, какое здесь правило вывода. Статический тип bPartialFunction[Any, String], и он несовместим с PartialFunction[String, String], если только вы не решите преобразовать Any в String. Почему компилятор это делает? Откуда подсказка? - person Gabriele Petronella; 20.08.2014
comment
Да, этот комментарий неправильный. Это очень совместимо или соответствует. - person som-snytt; 20.08.2014
comment
Два PartialFunction являются подтипами, но Any определенно не является подтипом String. Также Int не является подтипом String, но orElse работает для PF[Int, String] и PF[String, String]. - person Gabriele Petronella; 20.08.2014
comment
Спасибо, ваше последнее редактирование кажется вполне законным. Мне все еще нужно обработать всю информацию (и я мог бы даже попытаться написать ответ сам), но ваш вклад был очень полезным. - person Gabriele Petronella; 20.08.2014
comment
Я лично тоже особо не интересовался подобными вещами. Здесь я частично полагаюсь на свой здравый смысл. Был бы признателен за более подробный ответ с большим количеством знаний и фактов, а не инстинкта. - person Kigyo; 20.08.2014
comment
You should not think of A1 <: A as a restriction. Возможно, вы имеете в виду, что домены функций расширены, см. документы. Но аргументы типа задают ожидаемые типы для аргументов значений, поэтому то, что вы выбираете, имеет значение. scala-lang.org/api/current/ - person som-snytt; 20.08.2014
comment
Да, возможно, неудачная формулировка. Я просто хотел сказать, что не следует беспокоиться о том, что orElse неприменим. (Если вы не укажете типы вручную) - person Kigyo; 20.08.2014

Вы уже ввели это, но:

scala> val a: PartialFunction[String, String] = { case "a" => "b" }
a: PartialFunction[String,String] = <function1>

scala> val b: PartialFunction[Any, String] = { case 1 => "one" }
b: PartialFunction[Any,String] = <function1>

scala> a orElse b
res0: PartialFunction[String,String] = <function1>

scala> a orElse[String,String] b
res1: PartialFunction[String,String] = <function1>

scala> a orElse[Any,String] b
<console>:10: error: type arguments [Any,String] do not conform to method orElse's type parameter bounds [A1 <: String,B1 >: String]
              a orElse[Any,String] b
              ^

scala> import reflect.runtime._ ; import universe._
import reflect.runtime._
import universe._

scala> typeOf[PartialFunction[Any,String]] <:< typeOf[PartialFunction[String,String]]
res3: Boolean = true

Из-за параметра контравариантного типа вы можете использовать здесь PF[Any, String].

Чтобы ответить на вопрос, где сказано, что он выберет?

http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#local-type-inference

Например, он обещает вывести максимальное A1 в контравариантном положении, но все еще соответствующее ограничению ‹: A.

person som-snytt    schedule 20.08.2014
comment
Я получаю точку контравариантности, но все же Any не является подтипом String, так как же можно соблюдать границу A1 <:< A? - person Gabriele Petronella; 20.08.2014
comment
Я прокомментировал другой ответ. A1 и A являются строками. - person som-snytt; 20.08.2014
comment
Я имею в виду, очевидно, это потому, что параметр предполагаемого типа для A1 равен String, а не Any, но почему происходит такой вывод? - person Gabriele Petronella; 20.08.2014
comment
Потому что это работает? Извини, я бегу, чтобы забрать школу. Вы показали, что это не работает с A1 как Any. - person som-snytt; 20.08.2014
comment
Мне не хватает точного правила, по которому Any статически выводится как String. Я убежден, что объяснение правильное, но я все еще упускаю этот момент. - person Gabriele Petronella; 20.08.2014
comment
Не любой, просто А1. Попробуйте a orElse[Nothing, String] b. Типы в порядке, просто бесполезны. - person som-snytt; 20.08.2014
comment
также typeOf[PartialFunction[Int,String]] <:< typeOf[PartialFunction[String,String]] это false, но вы все равно можете их составить - person Gabriele Petronella; 20.08.2014
comment
Если вы выводите A1 из типа b, то вы должны вывести Any. Вопрос в том, откуда начинается цепочка вывода, которая приводит A1 к String? - person Gabriele Petronella; 20.08.2014
comment
Ну, я думаю, это так: у нас изначально есть String - Int, и оба варианта контравариантны. Поэтому, когда два типа являются контрвариантными, мы можем вывести общий подтип, который является подтипом обоих. В крайнем случае можно было бы смешать типы, такие как String with Int. - person Kigyo; 20.08.2014
comment
это также объясняет, почему результирующий тип возвращаемого значения функции — Any, который является наименее распространенным супертипом для двух ковариантных типов. Мне нужно обдумать это, но я думаю, что вы, наконец, убедили меня. - person Gabriele Petronella; 20.08.2014