2016-11-29 3 views
3

Предположим, что у меня есть функциональный тип, например.Получить «наиболее специфичный» тип ввода

trait Parser[-Context, +Out] 

, и я хочу, чтобы иметь возможность объединить несколько парсеров так что комбинированная Context будет наиболее конкретным типом среди контекстов объединившихся парсеров. Например:

Parser[Any, Int] + Parser[String, Long] = Parser[String, (Int, Long)] 
Parser[String, Int] + Parser[Any, Long] = Parser[String, (Int, Long)] 
Parser[Option[Int], Foo] + Parser[Some[Int], Bar] = Parser[Some[Int], (Foo, Bar)] 
Parser[String, Foo] + Parser[Int, Bar] = <should be a compile error> 

Чтобы поместить пример в более конкретных терминах, предположим, что у меня есть функция объединитель как

def zipFuncs[A, B1, B2](f1: A => B1, f2: A => B2): A => (B1, B2) = { 
    a => (f1(a), f2(a)) 
} 

и некоторые функции, такие как

val f1 = { a: Any => 123 } 
val f2 = { a: String => 123 } 
val f3 = { a: Option[Int] => 123 } 

Теперь я могу сделать

> zipFuncs(f1, f2) 
res1: String => (Int, Int) = <function> 

> zipFuncs(f1, f3) 
res2: Option[Int] => (Int, Int) = <function> 

> zipFuncs(f2, f3) 
res3: Option[Int] with String => (Int, Int) = <function1> 

B ut, что я хочу, это для zipFuncs(f2, f3), чтобы не компилировать вообще. Поскольку String не является подтипом Option[Int], а Option[Int] не является подтипом String, нет возможности построить входное значение для res3.

я создать класс типов:

// this says type `T` is the most specific type between `T1` and `T2` 
sealed trait MostSpecificType[T, T1, T2] extends (T => (T1, T2)) 
// implementation of `object MostSpecificType` omitted 

def zipFuncs[A, A1, A2, B1, B2](f1: A1 => B1, f2: A2 => B2)(
    implicit mst: MostSpecificType[A, A1, A2] 
): A => (B1, B2) = { a: A => 
    val (a1, a2) = mst(a) 
    f1(a1) -> f2(a2) 
} 

Это завершало цели, описанные выше, но с очень раздражает проблемой. IntelliJ будет выделять допустимые комбинации как ошибки, вызывая, что «наиболее специфический тип (A)» фактически равен Nothing, когда он фактически является реальной стоимостью. Here's the actual issue in practice.

Проблема с подсветкой, несомненно, является ошибкой в ​​IntelliJ, и поиск в google, по-видимому, подразумевает, что различные сбросы/кеш-файлы/etc должны исправлять его (это не так). Независимо от вины, я надеюсь найти альтернативный подход, который удовлетворяет моему первоначальному требованию и не путает IntelliJ.

ответ

2

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

def Zip[A,X,Y](f: A => X, g: A => Y): A => (X,Y) = a => (f(a), g(a)) 

implicit class ZipOps[A,X](val f: A => X) extends AnyVal { 

    def zip[A0, Y](g: A0 => Y)(implicit ev: A0 <:< A): A0 => (X,Y) = 
    Zip({a: A0 => f(a)},g) 

    def zip[A0 >: A, Y](g: A0 => Y): A => (X,Y) = 
    Zip(f,g) 

} 

val f1: Any => Int = { a: Any => 123 } 
val f2: String => Int = { a: String => 123 } 
val f3: Option[Int] => Int = { a: Option[Int] => 123 } 

val x1 = f1 zip f2 // works 
val x1swap = f2 zip f1 // works 
val x2 = f1 zip f3 // works 
val x3 = f2 zip f3 // cannot prove that Option[Int] <:< String 
val x3swap = f3 zip f2 // cannot prove that String <:< Option[Int] 
+0

Это точно поведение, которое я искал. Мне придется попробовать этот подход в моем реальном проекте! – Dylan

4

можно добиться того, что с помощью generalized type constraints:

def zipFuncs[A1, A2, B1, B2](f1: A1 => B1, f2: A2 => B2) 
          (implicit ev: A2 <:< A1): A2 => (B1, B2) = { 
    a => (f1(a), f2(a)) 
} 

val f1 = { a: Any => 123 } 
val f2 = { a: String => 123 } 
val f3 = { a: Option[Int] => 123 } 

zipFuncs(f1, f2) // works 
zipFuncs(f1, f3) // works 
zipFuncs(f2, f3) // cannot prove that Option[Int] <:< String 

Однако это требует второй функции, чтобы использовать более конкретный тип в качестве входного параметра, чем первый. Это нормально, если вы также не хотите работать zipFuncs(f2, f1). Если у вас есть это требование, я не вижу другого способа, чем делать некоторые implicit type gymnastics, подобные тем, которые вы уже делаете.

РЕДАКТИРОВКА: См. Ответ Эдуардо для аккуратного трюка по достижению этого.

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

+0

Приветствия для ответа. Рад узнать, что я не единственный, у кого проблемы с подсветкой, но грустно, что это проблема вообще. У меня было «это требование», поэтому я в конечном итоге принял ответ Эдуардо, но +1 за то, что у вас есть время – Dylan

+0

Нет проблем.Если кто-то не имеет понятия об ограничениях обобщенного типа, я думаю, что мой ответ немного легче увидеть эту идею, прежде чем проверять, как @Eduardo использовал их (наряду с некоторыми дополнительными трюками), чтобы заставить все это работать :) – slouc