Является ли PartialFunction extends Function нарушением LSP?

Принцип замещения Лискова гласит, что

если S является подтипом T, тогда объекты типа T могут быть заменены объектами типа S без изменения каких-либо желаемых свойств этой программы.

Однако в Scala есть PartialFunction, который применим / определен не во всех случаях.

trait Function1 {
  def apply(x: A): R
}

trait PartialFunction extends Function1 {
  def apply(x: A): R
  def isDefinedAt(x: A): Boolean
}

Если вы примените PartialFunction к неопределенному значению, вы получите исключение.

Удобный способ создать PartialFunction в scala - использовать сопоставление с образцом. При этом вы получите MatchError для неопределенных значений.

val fn:Function1[String, Int] = s => s.length

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

def program(f:Function1[String, Int], s:String):(Boolean, Int) = (
  f.isInstanceOf[Function1[String, Int]], f(s)
)

program(fn, "one") == program(pf, "one")
program(fn, "two") == program(pf, "two")

fn: String => Int = ‹function1>

pf: PartialFunction [String, Int] = ‹function1>

программа: программа [] (val f: String => Int, val s: String) => (Boolean, Int)

res0: Boolean = true

scala.MatchError: два (класса java.lang.String)

в scala.PartialFunction $$ anon $ 1.apply (delme.sc:249)

в scala.PartialFunction $$ anon $ 1.apply (delme.sc:247)

в ...

И fn, и pf являются подтипами Function1, но я не могу заменить fn на pf без изменения моего program. Так что на мой взгляд это нарушение LSP.

Что вы думаете ?


person gervais.b    schedule 03.11.2016    source источник
comment
Это будет в первую очередь мнение. У вас есть более конкретный вопрос относительно применения или использования более общего вопроса, который вы задаете?   -  person wheaties    schedule 03.11.2016
comment
Точно нет. Я просто спрашивал совета у других разработчиков. Может стоит выложить это в другом сообществе?   -  person gervais.b    schedule 03.11.2016
comment
Вы также можете определить Function1, который просто генерирует исключения для всех входных данных, кроме "one". Ваш аргумент в пользу нарушения LSP состоит в том, что генерирование исключения может быть нежелательным изменением, но Function1 все еще может иметь входы, которые генерируют исключения. например BigDecimal("abc"). Основное различие между PartialFunction и Function1 заключается в том, что у вас есть встроенный способ проверки того, определены ли элементы первыми.   -  person Michael Zajac    schedule 03.11.2016
comment
Никакие языки не могут предотвратить ошибки пользователя. Если кто-то хочет нарушить его программу, он всегда может это сделать. Меня беспокоит больше, чем, на мой взгляд, Scala (сам язык) нарушает LSP. Это никогда не сделает его менее крутым, но мне просто интересно.   -  person gervais.b    schedule 03.11.2016


Ответы (2)


fn и pf не являются подтипами Function1, поскольку они вообще не являются типами. Это значения, и LSP не говорит, что вы можете взять любой объект типа T и заменить его любым объектом типа S: Int, безусловно, является подтипом Int, но замена 1 на 2 может сделать правильную программу неверной.

PartialFunction[String, Int] является подтипом Function1[String, Int], но контракт Function1 не запрещает apply генерировать исключения и фактически явно разрешает это:

Примените тело этой функции к аргументу. Это может вызвать исключение.

Итак, если ваша программа может обрабатывать объекты типа Function1[A, B], она уже должна иметь дело с apply выдачей исключений каким-то образом, а PartialFunction выдача MatchError - это просто особый случай.

person Alexey Romanov    schedule 03.11.2016

Согласно Википедии, для применения LSP требуется дополнительный пункт.

Никакие новые исключения не должны генерироваться методами подтипа, за исключением случаев, когда эти исключения сами являются подтипами исключений, генерируемых методами супертипа.

Итак (при условии, что Википедия верна), никакая PartialFunction не нарушает LSP, поскольку она не применима к этому случаю.

РЕДАКТИРОВАТЬ: В скаладоке есть дополнительная информация: (Scaladoc Function1)

Обратите внимание, что Function1 не определяет общую функцию, как можно предположить из-за существования PartialFunction. Единственное различие между Function1 и PartialFunction состоит в том, что последний может указывать входные данные, которые он не будет обрабатывать.

Другими словами, pf не является подтипом fn.

fn: String => Int

pf: подмножество String => Int

Дальнейшее редактирование в ответ на комментарий: Нет, я утверждаю, что LSP вообще не применяется. Для применения LSP S не должен вызывать новых исключений. Здесь S выдает новые исключения, поэтому LSP не может быть нарушен, поскольку он не применяется.

person A Spoty Spot    schedule 03.11.2016
comment
Но PartialFunction бросает scala.MatchError, а Function нет? Или вы утверждаете, что Function может кидать что угодно? Конечно, исключения здесь не выражаются в типе, так что это может быть то же самое. - person Bergi; 03.11.2016
comment
Я добавил еще кое-что. Мои знания основ теории типов практически отсутствуют, поэтому я очень склонен к полной неправоте. - person A Spoty Spot; 03.11.2016
comment
Ах, я понял вашу цитату, поскольку LSP нарушается с таким поведением исключения, вместо того, чтобы вообще не применяться. - person Bergi; 03.11.2016
comment
Я не согласен: 1. это предложение выполнено, потому что Function1#apply действительно может вызвать любое исключение. Поскольку Scala не проверяет исключения, единственный способ их нарушения - это задокументировано не генерировать исключения, но это явно разрешено. - person Alexey Romanov; 03.11.2016
comment
2. Это не условия для применения LSP вообще, а условия выполнения (необходимые, но недостаточные). Т.е. если бы условие не было выполнено, это было бы нарушением LSP. - person Alexey Romanov; 03.11.2016