On variance status of Rule[-I, +O]

14 views
Skip to first unread message

Alexander Myltsev

unread,
Oct 12, 2016, 9:53:50 AM10/12/16
to parboiled2.org User List

Hi,

 

I am trying to understand possible negative consequences of using `@uncheckedVariance`.

 

I am looking at TailSwitch. Both `I` and `O` types (that are in contravariant and covariant positions respectively) are all used same positions in different usages of TailSwitch instances: `i: TailSwitch[I2, O, I]` and `o: TailSwitch[O, I2, O2]`. And to avoid code duplications TailSwitch just drops variance check. Is that the reason of using `@uncheckedVariance`?

 

If yes, then why `Lifter` drops variance check?


-------------- 


Another question that caused `@uncheckedVariance` is that `I` contravariance and `O` covariance in the `Rule` definition:

 

https://github.com/GlobalNamesArchitecture/parboiled2/blob/v2.1.0/parboiled-core/src/main/scala/org/parboiled2/Rule.scala#L36

 

That canonical Scala’s `Function`-fashion implementation states that, for example having

 

class Publication(val title: String)
 
class Book(title: String) extends Publication(title)

It is true that:

 

scala> implicitly[Rule[Publication :: HNil, String :: HNil] <:< Rule[Book :: HNil, AnyRef :: HNil]]
res4
: <:<[org.parboiled2.Rule[shapeless.::[org.parboiled2.examples.Publication,shapeless.HNil],shapeless.::[String,shapeless.HNil]],org.parboiled2.Rule[shapeless.::[org.parboiled2.examples.Book,shapeless.HNil],shapeless.::[AnyRef,shapeless.HNil]]] = <function1>


A `Rule` instance can’t be passed as argument (or can it?) being subtype of some other `Rule`. Why then parboiled2 need that type variance?


--------------

 

Also, let’s define some rules:

 

def r1: Rule[Publication :: HNil, String :: HNil] = rule { MATCH ~> { (p: Publication) => p.title } }
def r2: Rule[Book :: HNil, AnyRef :: HNil] = rule { MATCH ~> { (b: Book) => List(b.title) } }
def book: Rule[HNil, Book :: HNil] = rule { capture("b") ~> { (s: String) => new Book(s) } }
def pub:  Rule[HNil, Publication :: HNil] = rule { capture("b") ~> { (s: String) => new Publication(s) } }
def test1 = rule { book ~ r2 }
def test2 = rule { pub ~ r2 }
def test3 = rule { book ~ r1 }
def test4 = rule { pub ~ r1 }
def test5 = rule { book ~ (r1 | r2) }
def test6 = rule { book ~ (r2 | r1) }
def test7 = rule { pub ~ (r1 | r2) }
def test8 = rule { pub ~ (r2 | r1) }

 

`test2` should work, but fails at runtime with cast exception.

`test3` should not pass type check, but works brilliantly in runtime.

 

`test{5..8}` pass type check to runtime, where those give weird results.


Type check does not save us much here, doesn’t it?

Mathias Doenitz

unread,
Oct 13, 2016, 8:09:14 AM10/13/16
to parboil...@googlegroups.com
Alex,

the reasoning behind the variance markers on the type params of the `Rule` type is exactly the same
as the for the scala `Function1` type.
Essentially a `Rule` is like a function, consuming input and producing output.
The only difference is that a scala `Function` works on its method parameters whereas a `Rule` works on the value stack.

The reason for the @uncheckedVariance annotations is more of a technical implementation detail.
We *could* get rid of these annotations if we supplied the respective methods via an implicit extension rather than directly on the class.
But then the operations wouldn't be as discoverable by IDEs, which is why they where supplied on the class using @uncheckedVariance.

However, you still found a bug!
I just filed a ticket for it: https://github.com/sirthias/parboiled2/issues/172

There is a problem in the current application of `TailSwitch`, which computes the output types of the `~` operator.
It's interesting that this problem hasn't been found before.

Unfortunately I currently don't have time to dig into it myself at this point.

Thanks for digging and reporting!!

Cheers,
Mathias

---
mat...@parboiled.org
http://www.parboiled.org
> --
> You received this message because you are subscribed to the Google Groups "parboiled2.org User List" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to parboiled-use...@googlegroups.com.
> Visit this group at https://groups.google.com/group/parboiled-user.
> To view this discussion on the web visit https://groups.google.com/d/msgid/parboiled-user/283875d4-4ff9-437b-a1cf-422db7f4bb2b%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages