Should the PartialFunction has only one type parameter?

89 views
Skip to first unread message

scala solist

unread,
Sep 20, 2016, 11:36:49 PM9/20/16
to scala-user
Most scala type abstractions seems natural. co/contra variance forms consistent theory. But I could not make myself belive in the PartialFunction[-R,+T]. Why -R ? PartialFunction looks like it is univariant. Suppose you get A <: B <: C. You may easily pass (c : C) to a PartialFunction[B,T] as for any other function. But you could as easily pass (a : A) to the same function, it would be matched during runtime and if neither of case clauses is triggered, than there is just no match. orElse should extend argument domain and should not narrow it. Either left or right clause is triggered. What was the reasons to choose

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



Why not

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



suppose you get three partial functions

val fun1 = {
 
case x : Int => x + 1
 
case d : Double => (d + 1).toInt
}

val fun2
= {
 
case d : Double => (d + 1).toInt
 
case s : String => s.length
}

val fun3
= {
 
case x : Int => x + 1
 
case s : String => s.length
}



It is clearly a code duplication and should be rewritten as

val i = { case x : Int => x + 1 }
val d
= { case d : Double => (d + 1).toInt }
val s
= { case s : String => s.length }

val fun1
= i orElse d
val fun2
= d orElse s
val fun3
= i orElse s



But that brings you strait to the type inference issue. So you should manually specify the type of the partial function. And you should anticipate all possible partial function combinations beforehead, and choose the most common type for them. It seems very awkward. Just like it would be better to erase R type parameter completely and make PartialFunction[T] <: Function[Any,T]

So I have several questions. Have I missed something in partial function nature? I feel that the problem is not with the partial functions themself but with scala representation for them and it is a matter of the platform representation. Have I guessed right? If yes than there should exist clear description of the concept independent from specific language. There may exist even an implementation in programming language at the best case. Could you point it for me?

Stephen Compall

unread,
Sep 26, 2016, 5:09:25 AM9/26/16
to scala solist, scala-user
On 9/21/16 10:36 AM, scala solist wrote:
Most scala type abstractions seems natural. co/contra variance forms consistent theory. But I could not make myself belive in the PartialFunction[-R,+T]. Why -R ? PartialFunction looks like it is univariant. Suppose you get A <: B <: C. You may easily pass (c : C) to a PartialFunction[B,T] as for any other function.

I don't think this is hugely relevant to addressing the rest of your comments, but to be clear, you cannot do this.  Possibly you have just reversed A and C in your description, and I will assume that for the rest of this message.

Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_92).
Type in expressions for evaluation. Or try :help.

scala> def test[C, B <: C, A <: B, T](pf: PartialFunction[B, T], c: C) =
     |   pf(c)
<console>:12: error: type mismatch;
 found   : c.type (with underlying type C)
 required: B
         pf(c)
            ^
scala> def test[C, B <: C, A <: B, T](pf: PartialFunction[B, T], a: A) =
     |   pf(a)
test: [C, B <: C, A <: B, T](pf: PartialFunction[B,T], a: A)T

But you could as easily pass (a : A) to the same function, it would be matched during runtime and if neither of case clauses is triggered, than there is just no match. <snip>


So I have several questions. Have I missed something in partial function nature?

I think you have missed something, and I'll try to explain it in a couple ways.

From runtime discrimination being impossible

One issue boils down to [compile-time] types and [runtime] classes not being the same thing.  So given two types A and B, and a value x: Any, it is not always possible to determine whether x is A, B, or neither.  So when you mention "it would be matched during runtime...", this does not account for all cases.  So if the PartialFunction model assumed this invariant, it could not be used with all types.  It is more desirable that it work with all types.

The fact of inability to type-test does not depend on "erasure"; even if all types were reified, it would still be impossible in some cases, because the type model contains features having to do with scope that do not exist in a reified class model. I will not elaborate further here except to say keep a lookout for "There are more types than classes".

From runtime discrimination being undesirable

I feel that the problem is not with the partial functions themself but with scala representation for them and it is a matter of the platform representation. Have I guessed right? If yes than there should exist clear description of the concept independent from specific language.

It's not a problem with Scala's representation; rather, it is a feature that exists in the underlying concept.  Let's call this independent concept PF, to distinguish from the broader term "partial function".

A PF of T domain and R codomain consists of three components:
  1. an existential type M,
  2. a function T -> M option, which is a conceptual discriminator for values of type T, and
  3. a function M -> R.

Just as A -> B says that A is the type of arguments the function is capable of handling, for PF, T is the type of arguments that the PF is capable of discriminating among.

Suppose you have a computer vision system on an assembly line; it recognizes parts of an assembled machine and checks how close they are to spec, classifying the machine, accept, reject, adjust, &c.  And then a cat walks into the camera view.  Is it meaningful to ask for the vision system's classification of the cat?  Not really.

This model gives you a tool to say either "yes" if desirable (via setting T = the top type), but usually the answer is "no", and that is modeled by choosing an appropriate T, which is below the top type.  So the presence of T in the type model lets you more precisely model what you mean, i.e. ban the cats from the assembly floor.

--
Stephen Compall
If anyone in the MSA is online, you should watch this flythrough.

scala solist

unread,
Sep 28, 2016, 12:34:21 AM9/28/16
to scala-user
You have described PartialFunction as container for the isDefinedAt and the apply methods. Looking at their signature, you really could tell, that there should be contravariant R type signature. But what if look at partial function as container for case clauses?

Stephen Compall

unread,
Sep 30, 2016, 4:05:45 AM9/30/16
to scala solist, scala-user
On 9/28/16 11:34 AM, scala solist wrote:
You have described PartialFunction as container for the isDefinedAt and the apply methods. Looking at their signature, you really could tell, that there should be contravariant R type signature. But what if look at partial function as container for case clauses?

(Noting for posterity that we are discussing representation in the "is undesirable" section, setting aside "is impossible", which was not addressed in the quoted message)

While the abstract representation I outlined has two functions in it, in which T and R appear in contravariant and covariant position respectively, the similarities to isDefinedAt/apply end there.

M is existential (an abstract type member), so you can derive different PF representations from various [type-level] values.  As such, (∃M. (TM option, MR)) subsumes them all.
  1. What if M = T?  (This is the one, of many, where isDefinedAt/apply may be used for representation.)
  2. What if M = () → R?
  3. What if M = (T, int)?  (This comes to your "container of case clauses"; the opacity of M saves you at compile-time from partiality of collection indexing.  Really "int" is some Fin'd natural number.)
  4. What if M = some custom private ADT defined specific to a PF based on its cases?  What would such an ADT look like?
  5. Implement ??? in the below code:
trait PF[-T, +R] {self =>
  type M
  val discriminate: T => Option[M]
  val complete: M => R

  def orElse[TT <: T, RR >: R](o: PF[TT, RR]): PF[TT, RR] =
    new PF[TT, RR] {
      type M = Either[self.M, o.M]
      override val discriminate = ???
      override val complete = ???
    }
}

More intuitively, but by no means replacing the above discussion, a single case clause is still a specialization of PF. A List[T => R] still puts T in contravariant position, however the underlying contravariant position comes about.

Reply all
Reply to author
Forward
0 new messages