Почему PartialFunction ‹: Function в Scala?

В Scala класс PartialFunction[A, B] является производным от типа Function[A, B] (см. Справочник по Scala, 12.3.3). Однако мне это кажется нелогичным, поскольку Function (который должен быть определен для всех A) имеет более строгие требования, чем PartialFunction, который в некоторых местах может быть неопределенным.

Проблема, с которой я столкнулся, заключалась в том, что когда у меня есть частичная функция, я не могу использовать Function для расширения частичной функции. Например. Я не могу сделать:

(pf orElse (_)=>"default")(x)

(Надеюсь, синтаксис хотя бы отдаленно правильный)

Почему это подтипирование выполняется в обратном порядке? Есть ли какие-то причины, которые я упустил из виду, например тот факт, что типы Function встроены?

Кстати, было бы неплохо, если бы Function1 :> Function0 так что мне не нужен фиктивный аргумент в приведенном выше примере :-)

Изменить, чтобы прояснить проблему подтипа

Разницу между двумя подходами можно подчеркнуть, рассмотрев два примера. Кто из них прав?

Один:

val zeroOne : PartialFunction[Float, Float] = { case 0 => 1 }
val sinc = zeroOne orElse ((x) => sin(x)/x) // should this be a breach of promise?

Два:

def foo(f : (Int)=>Int) {
  print(f(1))
}
val bar = new PartialFunction[Int, Int] {
  def apply(x : Int) = x/2
  def isDefinedAt(x : Int) = x%2 == 0
}
foo(bar) // should this be a breach of promise?

person jpalecek    schedule 30.05.2009    source источник
comment
Был ли дан ответ на этот вопрос или он все еще открыт?   -  person James Iry    schedule 18.06.2009
comment
Я не знаю. Я подумываю о том, чтобы закрыть его, потому что я думаю, что это было бы лучше в каком-нибудь списке рассылки или что-то в этом роде...   -  person jpalecek    schedule 25.06.2009


Ответы (2)


Потому что в Scala (как и в любом языке, полном по Тьюрингу) нет гарантии, что Функция является тотальной.

val f = {x : Int => 1 / x}

Эта функция не определена в 0. PartialFunction — это просто функция, которая обещает сообщить вам, где она не определена. Тем не менее, Scala позволяет достаточно просто делать то, что вы хотите.

def func2Partial[A,R](f : A => R) : PartialFunction[A,R] = {case x => f(x)}

val pf : PartialFunction[Int, String] = {case 1 => "one"} 

val g = pf orElse func2Partial{_ : Int => "default"}

scala> g(1)
res0: String = one

scala> g(2)
res1: String = default

Если вы предпочитаете, вы можете сделать func2Partial неявным.

person James Iry    schedule 30.05.2009
comment
Этот момент был бы верным, если бы PartialFunction действительно давал обещание сообщить, где он не определен. Однако я не уверен, что это действительно так. Справочник говорит, что PartialFunction является функцией, не определенной в некоторых точках. Я не думаю, что это означает, что такая функция, как { case x:Int => 1/x }, недействительна (потому что она не выполняет обещание), просто исключение является ее предполагаемым поведением. - person jpalecek; 01.06.2009
comment
Формально говоря, f(x) = 1/x является частичной функцией, если предметная область включает 0. Поскольку система типов Scala не позволяет вам говорить Int, кроме 0, то то, что я написал, является частичной функцией. Это просто не объект PartialFunction, потому что у него нет метода, сообщающего вызывающим объектам, где он находится и где он не определен. - person James Iry; 01.06.2009
comment
Но Scala не требует, чтобы функция была полной, и не требует, чтобы частичная функция возвращала значение, когда она говорит, что она определена. Что мне трудно понять, так это то, что вы говорите, что PF дает обещание; пока я думаю, что это делает ограничение. Не могли бы вы дать несколько советов по этому поводу? Кстати, частичная функция {case x:Int => 1/x} действительна или нет? - person jpalecek; 04.06.2009
comment
Scala не может заставить вас не лгать в вашем методе isDefinedAt. Таким образом, Scala считает вашу PartialFunction действительной, но пользователи будут раздражены тем, что вы сделали это неправильно. scala› val pf : PartialFunction[Any,Int] = {case x : Int if x != 0 =› 1/x} pf: PartialFunction[Any,Int] = ‹function› scala› pf.isDefinedAt(hello) res2: Boolean = false scala› pf.isDefinedAt(0) res3: Boolean = false scala› pf.isDefinedAt(1) res4: Boolean = true - person James Iry; 04.06.2009
comment
Вы продолжаете говорить мне о лжи, но я все еще не понимаю, где спецификация Scala говорит, что PartialFunction выдает исключение в точке, где isDefinedAt() лежит. Например, в абзаце о try-выражениях в Справке говорится... ожидается, что обработчик будет соответствовать типу PartialFunction[Throwable, pt]... Итак, что-то вроде try something catch { case x:SomeException => throw OtherException } недопустимо, потому что обработчик выдает исключение, учитывая точку из своего домена? Или в каком случае функция достаточно определена, чтобы не лгать? - person jpalecek; 25.06.2009
comment
В CS любая функция, которая может генерировать исключение для некоторых входных данных, является частичной*. Это касается даже вашей функции, которая должна переводить исключение во что-то, но вместо этого выдает другое исключение. Это не плохо автоматически. Возможно, это правильный выбор. На данный момент у меня складывается впечатление, что вы не хотите получать ответ на свой первоначальный вопрос - вы просто хотите обсудить выбор дизайна. Пожалуйста, посетите список рассылки scala-debate, команда слушает. * Как правило, такие вещи, как OOM, которые могут произойти в любом месте и в любое время, игнорируются для такого рода обсуждений. - person James Iry; 25.06.2009

PartialFunction имеет методы, которых нет у Function1, поэтому это подтип. Это методы isDefinedAt и orElse.

Ваша настоящая проблема заключается в том, что PartialFunction иногда не выводятся, когда вы действительно этого хотите. Я надеюсь, что это будет рассмотрено в будущем. Например, это не работает:

scala> val pf: PartialFunction[String, String] = { case "a" => "foo" }
pf: PartialFunction[String,String] = <function>

scala> pf orElse { case x => "default" }
<console>:6: error: missing parameter type for expanded function 
((x0$1) => x0$1 match { case (x @ _) => "default" })

Но это делает:

scala> pf orElse ({ case x => "default" } : PartialFunction[String,String])
res5: PartialFunction[String,String] = <function>

Конечно, вы всегда можете сделать это:

scala> implicit def f2pf[T,R](f: Function1[T,R]): PartialFunction[T,R] = 
  new PartialFunction[T,R] { 
    def apply(x: T) = f(x)
    def isDefinedAt(x: T) = true 
  }
f2pf: [T,R](f: (T) => R)PartialFunction[T,R]

И теперь это больше похоже на то, что вы хотите:

scala> pf orElse ((x: String) => "default")
res7: PartialFunction[String,String] = <function>

scala> println(res7("a") + " " + res7("quux"))
foo default
person psp    schedule 03.06.2009
comment
Извините, но я не согласен, что у PartialFunction есть методы, которых нет у Function1, поэтому это аргумент подтипа. Это как сказать, что если у вас есть класс EvenInteger со сложением, умножением и равенством, а также класс Integer со сложением, умножением, равенством и дополнительным методом isEven (оба неизменяемые), то дополнительный метод делает Integer ‹: EvenInteger, а на самом деле это как раз наоборот. - person jpalecek; 04.06.2009
comment
Я не возражаю против того, чтобы его можно было смоделировать в любом направлении, но на самом деле я не понимаю, почему вы считаете, что одно направление явно более разумно, чем другое. На практике функции во много-много раз более распространены, чем частичные функции, и, поскольку они оба являются типажами, код дублируется в каждом классе, который их использует. Так что есть и технические причины, по которым PF должен быть специализацией F1, а не наоборот. Но было бы неплохо, если бы они были более легко взаимозаменяемыми. - person psp; 04.06.2009