2015-07-02 2 views
9

Я использую бесформенные и имеют следующий метод, чтобы вычислить разницу между двумя HLists:Shapeless HList проверка

def diff[H <: HList](lst1: H, lst2:H):List[String] = (lst1, lst2) match { 
    case (HNil, HNil)     => List() 
    case (h1::t1, h2::t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1, t2) 
    case (h1::t1, h2::t2)    => diff(t1, t2) 
    case _       => throw new RuntimeException("something went very wrong") 
    } 

Поскольку оба параметра метода принимают H, я бы ожидать HLists различных типов не скомпилируйте здесь. Например:

diff("a" :: HNil, 1 :: 2 :: HNil) 

не должны компилировать, но он делает, и это приводит к ошибке во время выполнения: java.lang.RuntimeException: something went very wrong. Есть ли что-то, что я могу сделать для параметров типа, чтобы этот метод принимал только две стороны с одинаковыми типами?

+0

Вы, похоже, не обрабатываете случай, когда пустая строка только одного из 'lst1' или' lst2', что вполне может объяснить вашу ошибку. –

+0

Я понимаю ошибку, но мне нужна ошибка компиляции, а не время выполнения. – triggerNZ

+1

О, я вижу, чего вы пытаетесь достичь. К сожалению, базовая черта 'HList' непараметризирована, и поэтому в вашем методе вызов' H' просто разрешен для 'Hlist' (который действительно является супертипом любого' Hlist', независимо от конкретных типов элементов). См. Мой ответ. –

ответ

7

К сожалению, базовая HList черта непараметризованная, и поэтому в вашем вызове методы H просто решила Hlist (который на самом деле является родительским любой Hlist независимо от конкретных типов элементов). Чтобы исправить это, мы должны изменить определение несколько, и вместо этого полагаются на обобщенных ограничений типа:

def diff[H1 <: HList, H2 <: HList](lst1: H1, lst2: H2)(implicit e: H1 =:= H2): List[String] = (lst1, lst2) match { 
    case (HNil, HNil)     => List() 
    case (h1::t1, h2::t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1, t2) 
    case (h1::t1, h2::t2)    => diff(t1, t2) 
    case _       => throw new RuntimeException("something went very wrong") 
} 

Давайте проверим:

scala> diff("a" :: HNil, 1 :: 2 :: HNil) 
<console>:12: error: Cannot prove that shapeless.::[String,shapeless.HNil] =:= shapeless.::[Int,shapeless.::[Int,shapele 
       diff("a" :: HNil, 1 :: 2 :: HNil) 
       ^

scala> diff("a" :: HNil, "b" :: HNil) 
res5: List[String] = List(a -> b) 

scala> diff("a" :: 1 :: HNil, "b" :: 2 :: HNil) 
res6: List[String] = List(a -> b, 1 -> 2) 

Теперь мы могли бы еще «обмануть» и явно установить H1 и H2 до HList, и мы вернемся к первому.

scala> diff[HList, HList]("a" :: HNil, 1 :: 2 :: HNil) 
java.lang.RuntimeException: something went very wrong 
    at .diff(<console>:15) 
    at .diff(<console>:13) 

К сожалению, я не думаю, что это легко разрешимо (это, конечно, правда, но у меня нет быстрого решения).

+0

Спасибо! Это именно то, что я хотел – triggerNZ

+0

Существует [несколько более простое решение] (http://stackoverflow.com/a/31192042/334519). –

1

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

object diff { 
    class Differ[T <: HList](val diff: (T, T) => List[String]) 

    def apply[T <: HList](l1: T, l2: T)(implicit differ: Differ[T]): List[String] = differ.diff(l1, l2) 

    implicit object NilDiff extends Differ[HNil]((_, _) => Nil) 

    implicit def ConsDiff[H, T <: HList : Differ] = new Differ[H :: T]({ 
     case (h1 :: t1, h2 :: t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1, t2) 
     case (h1 :: t1, h2 :: t2) => diff(t1, t2) 
    }) 
    } 

Это, безусловно, гораздо сложнее, чем выше один, и я пытался использовать Polymorphic function, но не мог закончить с надлежащей рекурсией скомпилированной.

10

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

def diff[H <: HList](lst1: H)(lst2: H): List[String] = (lst1, lst2) match { 
    case (HNil, HNil)     => List() 
    case (h1::t1, h2::t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1)(t2) 
    case (h1::t1, h2::t2)    => diff(t1)(t2) 
    case _       => throw new RuntimeException("bad!") 
} 

Который дает нам то, мы хотим:

scala> diff("a" :: HNil)(1 :: 2 :: HNil) 
<console>:15: error: type mismatch; 
found : shapeless.::[Int,shapeless.::[Int,shapeless.HNil]] 
required: shapeless.::[String,shapeless.HNil] 
     diff("a" :: HNil)(1 :: 2 :: HNil) 
         ^

Это работает (т.е. не компилирует ненадо, а затем взорвать во время выполнения), так как вывод типа Scala для методов работы на каждый параметр списков. Если lst1 и lst2 находятся в том же списке параметров, то H будет считаться их наименьшей верхней границей, что обычно не является тем, что вы хотите.

Если вы положили lst1 и lst2 в отдельных списках параметров, то компилятор будет решать, что H как только он видит lst1. Если lst2 не имеет того же типа, он взрывается (к чему мы стремимся).

Вы все еще можете сломать это, явно указав H на HList, но это на вашей собственной голове, я боюсь.

+0

Да, это проблема вывода типа. И да, введение второго списка параметров - это способ преодолеть это. На самом деле это то, что мое решение использует (в этом случае второй список параметров содержит неявные доказательства, а не 'lst2'). Ваше решение немного проще, и мое немного более естественно использовать (я думаю). –

Смежные вопросы