Подтвердить что ты не робот

Существуют ли какие-либо фундаментальные ограничения, которые останавливают Scala от реализации сопоставления шаблонов по функциям?

В таких языках, как SML, Erlang и в buch других, мы можем определить такие функции:

fun reverse [] = []
|   reverse x :: xs  = reverse xs @ [x];

Я знаю, что мы можем написать аналог в Scala, как это (и я знаю, что есть много недостатков в коде ниже):

def reverse[T](lst: List[T]): List[T] = lst match {
  case Nil     => Nil
  case x :: xs => reverse(xs) ++ List(x)
}

Но мне интересно, если бы мы могли написать прежний код в Scala, возможно, с desugaring к последнему.

Существуют ли какие-либо фундаментальные ограничения для такого синтаксиса, которые будут реализованы в будущем (я имею в виду, действительно фундаментальный - например, как вывод типа работает в Scala или что-то еще, за исключением парсера, очевидно)?

UPD
Вот фрагмент того, как он может выглядеть:

type T
def reverse(Nil: List[T]) = Nil
def reverse(x :: xs: List[T]): List[T] = reverse(xs) ++ List(x)
4b9b3361

Ответ 1

Это действительно зависит от того, что вы подразумеваете под фундаментальным.

Если вы действительно спрашиваете: "Если есть технический showstopper, который будет препятствовать реализации этой функции", тогда я бы сказал, что ответ нет. Вы говорите об обесцвечивании, и вы на правильном пути здесь. Все, что нужно сделать, - это в основном сшить несколько разделов на одну единственную функцию, и это может быть сделано как простой шаг предварительной обработки (для этого требуется только синтаксическое знание, нет необходимости в семантическом знании). Но для этого, даже имея смысл, я бы определил несколько правил:

  • Функциональная подпись обязательна (в Haskell, например, это будет необязательно, но всегда необязательно, определяете ли вы функцию сразу или в нескольких частях). Мы могли бы попытаться договориться о том, чтобы жить без подписи и попытаться извлечь ее из разных частей, но отсутствие информации о типе быстро зашло бы на нас. Более простой аргумент состоит в том, что, если мы хотим попытаться вывести неявную подпись, мы могли бы также сделать это для всех методов. Но правда в том, что есть очень веские причины иметь явные singatures в scala, и я не могу себе представить, чтобы это изменить.
  • Все части должны быть определены в пределах одного и того же объема. Для начала они должны быть объявлены в том же файле, потому что каждый исходный файл скомпилирован отдельно, и, таким образом, простого препроцессора было бы недостаточно для реализации этой функции. Во-вторых, в конце концов, мы все-таки заканчиваем одним методом, поэтому вполне естественно иметь все части в одной области.
  • Перегрузка невозможна для таких методов (в противном случае нам нужно будет повторить подпись для каждой части, чтобы препроцессор знал, какая часть принадлежит перегрузке)
  • Детали добавляются (сшиты) к сгенерированному match в том порядке, в котором они объявлены

Итак, вот как это могло бы выглядеть:

def reverse[T](lst: List[T]): List[T] // Exactly like an abstract def (provides the signature)
// .... some unrelated code here...
def reverse(Nil) = Nil
// .... another bit of unrelated code here...
def reverse(x :: xs ) = reverse(xs) ++ List(x)

Который может быть тривиально преобразован в:

def reverse[T](list: List[T]): List[T] = lst match {
  case Nil     => Nil
  case x :: xs => reverse(xs) ++ List(x)
}
// .... some unrelated code here...
// .... another bit of unrelated code here...

Нетрудно видеть, что приведенное выше преобразование очень механическое и может быть выполнено путем простого управления источником AST (AST, созданным слегка модифицированной грамматикой, которая принимает эти новые конструкции), и превращая его в целевую AST ( AST, разработанный стандартной грамматикой scala). Затем мы можем скомпилировать результат как обычно.

Итак, вы можете пойти с несколькими простыми правилами, мы можем реализовать препроцессор, который выполняет всю работу по реализации этой новой функции.


Если по фундаментальному вы спрашиваете: "Есть ли что-нибудь, что сделало бы эту особенность неуместной", тогда можно утверждать, что это не очень сильно scala. Но, кстати, это не приносит столько же внимания. scala автор фактически склонен к упрощению языка (как и к менее встроенным функциям, попытка переместить некоторые встроенные функции в библиотеки) и добавление нового синтаксиса, который не является более читаемым, идет вразрез с целью упрощение.

Ответ 2

В SML ваш фрагмент кода представляет собой буквально просто синтаксический сахар ( "производная форма" в терминологии спецификации языка) для

val rec reverse = fn x =>
    case x of [] => []
            | x::xs  = reverse xs @ [x]

который очень близок к коду Scala, который вы показываете. Итак, нет никакой "фундаментальной" причины, по которой Scala не может предоставить такой же синтаксис. Основная проблема заключается в необходимости Scala для дополнительных аннотаций типа, что делает этот сокращенный синтаксис гораздо менее привлекательным в целом и, вероятно, не стоит того.

Обратите также внимание на то, что конкретный синтаксис, который вы предлагаете, не будет хорошо летать, потому что нет возможности синтаксически отличать одно из двух аргументов от двух перегруженных функций. Вероятно, вам понадобится альтернативный синтаксис, похожий на SML, используя "|".

Ответ 3

Я не знаю SML или Erlang, но я знаю Haskell. Это язык без перегрузки методов. Перегрузка метода в сочетании с таким сопоставлением шаблонов может привести к двусмысленности. Представьте следующий код:

def f(x: String) = "String "+x
def f(x: List[_]) = "List "+x

Что это значит? Это может означать перегрузку метода, то есть метод определяется во время компиляции. Это также может означать сопоставление образцов. Будет только метод f (x: AnyRef), который будет выполнять сопоставление.

Scala также назвал параметры, которые, вероятно, также были бы нарушены.

Я не думаю, что Scala может предложить более простой синтаксис, чем вы показали в целом. Более простой синтаксис может ИМХО работать только в некоторых особых случаях.

Ответ 4

Есть как минимум две проблемы:

  • [ и ] являются зарезервированными символами, потому что они используются для аргументов типа. Компилятор допускает пробелы вокруг них, поэтому это не будет вариант.
  • Другая проблема заключается в том, что = возвращает Unit. Таким образом, выражение после | не вернет результат

Самое близкое, что я мог придумать, это (обратите внимание, что это очень специализировано в вашем примере):

// Define a class to hold the values left and right of the | sign
class |[T, S](val left: T, val right: PartialFunction[T, T])

// Create a class that contains the | operator
class OrAssoc[T](left: T) {
  def |(right: PartialFunction[T, T]): T | T = new |(left, right)
}

// Add the | to any potential target
implicit def anyToOrAssoc[S](left: S): OrAssoc[S] = new OrAssoc(left)

object fun {

  // Use the magic of the update method
  def update[T, S](choice: T | S): T => T = { arg =>
    if (choice.right.isDefinedAt(arg)) choice.right(arg)
    else choice.left
  }
}

// Use the above construction to define a new method
val reverse: List[Int] => List[Int] =
  fun() = List.empty[Int] | {
    case x :: xs => reverse(xs) ++ List(x)
  }

// Call the method
reverse(List(3, 2, 1))