PartialFunction expected, but I get function - Bug in Scala?!

146 views
Skip to first unread message

Stefano Di Martino

unread,
Sep 30, 2014, 10:17:47 AM9/30/14
to scala-l...@googlegroups.com
I define a partial function

val squareRoot = new PartialFunction[Double, Double] {
    def isDefinedAt(x: Double) = x >= 0
    def apply(x: Double) = scala.math.sqrt(x)
}

And then I define

val f1= squareRoot andThen sqr
> f1: PartialFunction[Double,Double] = <function1>
 
Seems logic, that f1 is a PartialFunction, because f1(-2) is not defined.

And then I define

val f3= sqr compose squareRoot
> f3: (Double) => Double = <function1>

f3 is a normal function, but f3(-2) is not defined! Why is f1 a PartialFunction and f3 not?!

Is this a bug in scala?!

Best regards,
Stefano

Stefan Höck

unread,
Sep 30, 2014, 10:34:45 AM9/30/14
to scala-l...@googlegroups.com
The 'bug' is that PartialFunction extends Function1 and thus it
is possible to write

sqr compose squareRoot

Function `compose` takes as argument a `Function1` and returns
a new `Function1`. It should not be possible to pass a `PartialFunction`
as an argument to `compose` but - alas! - here the designers of
Scala opted for convenience and sacrificed correctness.

My advice: Define your own wrapper

final class PFunction[A,B](val v: A => Option[B]) extends AnyVal

as a sound and correct alternative to `PartialFunction`.
PFunction has a Monad and Arrow instance so you can define
`map`, `flatMap` and some other useful functions for it.

Cheers, Stefan

Stefano Di Martino

unread,
Sep 30, 2014, 11:01:02 AM9/30/14
to scala-l...@googlegroups.com, efasc...@gmail.com
Strange behavior, but thx!

Regards,
Stefano

Andrew Phillips

unread,
Sep 30, 2014, 11:02:59 AM9/30/14
to scala-l...@googlegroups.com
> f3 is a normal function, but f3(-2) is not defined! Why is f1 a PartialFunction and f3 not?!

I guess the "answer" (but not the justification) is that PartialFunction.compose [1] does not return a PartialFunction, but a Function1:

"Composes two instances of Function1 in a new Function1, with this function applied last."

I'm afraid I can't shed any light on why there is no variant of compose that, similar to orElse, takes a partial function as an argument and returns another partial function, but I'm sure that would not be hard to write.

ap

Alec Zorab

unread,
Sep 30, 2014, 1:22:15 PM9/30/14
to scala-l...@googlegroups.com
/**  Composes this partial function with a transformation function that
* gets applied to results of this partial function.
* @param k the transformation function
* @tparam C the result type of the transformation function.
* @return a partial function with the same domain as this partial function, which maps
* arguments `x` to `k(this(x))`.
*/
override def andThen[C](k: B => C): PartialFunction[A, C] =
new AndThen[A, B, C] (this, k)

--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alec Zorab

unread,
Sep 30, 2014, 1:23:02 PM9/30/14
to scala-l...@googlegroups.com
... observe my overly trigger happy behaviour. That's still not right!

Paolo Giarrusso

unread,
Oct 23, 2014, 8:34:24 PM10/23/14
to scala-l...@googlegroups.com, efasc...@gmail.com
On Tuesday, September 30, 2014 4:34:45 PM UTC+2, Stefan Hoeck wrote:
The 'bug' is that PartialFunction extends Function1 and thus it
is possible to write

  sqr compose squareRoot

Function `compose` takes as argument a `Function1` and returns
a new `Function1`. It should not be possible to pass a `PartialFunction`
as an argument to `compose` but - alas! - here the designers of
Scala opted for convenience and sacrificed correctness.

No, you misunderstand Function1 (as I also used to do, https://issues.scala-lang.org/browse/SI-5370). They chose correctness and sacrificed the principle of least surprise, though making "Function1" total is not really possible in Scala. However, the documentation is still not adequate.

A Function1 can be partial. PartialFunction is a subtype of Function1 because it adds isDefinedAt, which is supposed to allow testing for the domain. From Function1 docs:

> Note that Function1 does not define a total function, as might be suggested by the existence of scala.PartialFunction. The only distinction between Function1 andPartialFunction is that the latter can specify inputs which it will not handle.


From PartialFunction docs:
Even if isDefinedAt returns true for an a: A, calling apply(a) may still throw an exception

To be sure, the following is misleading:
> A partial function of type 
PartialFunction[A, B] is a unary function where the domain does not necessarily include all values of type A.

Feel free to submit PRs to improve the documentation. I'm trying with https://github.com/scala/scala/pull/4074.

To be sure, that's an annoying API if you want to be sure you deal with total functions — but then you should either solve the halting problem (news: you can't), use a non-Turing-complete language (see Coq/Agda/Idris).

My advice: Define your own wrapper

  final class PFunction[A,B](val v: A => Option[B]) extends AnyVal

as a sound and correct alternative to `PartialFunction`.

Caveat: v should never have any side effect, nontermination included, and that's something which can't be checked automatically (unless you're ready to false positives, see non-Turing-complete languages above).

Stefan Höck

unread,
Oct 23, 2014, 11:50:48 PM10/23/14
to scala-l...@googlegroups.com
Thanks for the clarifications, Paolo. I understand now that the goal of
PartialFunction is to allow one to check whether a value is in its
domain without evaluating the - possibly lengthy - calculation;
something that would not be possible with A => Option[B].

Am I right in assuming that the same thing would be possible by using
a lazy implementation of `Option` (for instance scalaz.LazyOption,
which als defines a function `isDefined` that does not evaluate the
wrapped value), in which case we could use A => LazyOption[B] instead of
PartialFunction?

Cheers, Stefan

On Thu, Oct 23, 2014 at 05:34:24PM -0700, Paolo Giarrusso wrote:
> On Tuesday, September 30, 2014 4:34:45 PM UTC+2, Stefan Hoeck wrote:
> >
> > The 'bug' is that PartialFunction extends Function1 and thus it
> > is possible to write
> >
> > sqr compose squareRoot
> >
> > Function `compose` takes as argument a `Function1` and returns
> > a new `Function1`. It should not be possible to pass a `PartialFunction`
> > as an argument to `compose` but - alas! - here the designers of
> > Scala opted for convenience and sacrificed correctness.
> >
>
> No, you misunderstand Function1 (as I also used to do,
> https://issues.scala-lang.org/browse/SI-5370). They chose correctness and
> sacrificed the principle of least surprise, though making "Function1" total
> is not really possible in Scala. However, the documentation is still not
> adequate.
>
> A Function1 can be partial. PartialFunction is a subtype of Function1
> because it adds isDefinedAt, which is supposed to allow testing for the
> domain. From Function1 docs:
>
> > Note that Function1 does not define a total function, as might be
> suggested by the existence of scala.PartialFunction
> <http://www.scala-lang.org/api/2.11.2/scala/PartialFunction.html>. The only

Rex Kerr

unread,
Oct 24, 2014, 2:02:41 AM10/24/14
to scala-l...@googlegroups.com
On Thu, Oct 23, 2014 at 8:50 PM, Stefan Höck <efasc...@gmail.com> wrote:
Thanks for the clarifications, Paolo. I understand now that the goal of
PartialFunction is to allow one to check whether a value is in its
domain without evaluating the - possibly lengthy - calculation;
something that would not be possible with A => Option[B].

Am I right in assuming that the same thing would be possible by using
a lazy implementation of `Option` (for instance scalaz.LazyOption,
which als defines a function `isDefined` that does not evaluate the
wrapped value), in which case we could use A => LazyOption[B] instead of
PartialFunction?

It's possible, but also a bad idea since you now have to create two extra objects: LazyOption itself and the generating function that closes over the instance of A.

  --Rex
 

Reply all
Reply to author
Forward
0 new messages