Вопрос Scala против F #: как они объединяют парадигмы OO и FP? - PullRequest
40 голосов
/ 25 мая 2010

Каковы основные различия между подходами, принятыми Scala и F # для унификации парадигм OO и FP?

EDIT

Каковы относительные достоинства и недостатки каждого подхода? Если, несмотря на поддержку подтипов, F # может определять типы аргументов функций, тогда почему Scala не может?

Ответы [ 7 ]

43 голосов
/ 26 мая 2010

Я смотрел на F #, занимался низкоуровневыми уроками, поэтому мои знания очень ограничены. Тем не менее, для меня было очевидно, что его стиль был по сути функциональным, а ОО был скорее дополнением - гораздо больше модульной системы ADT +, чем настоящий ОО. Чувство, которое я испытываю, лучше всего описать так, как если бы все методы были статическими (как в Java static).

См., Например, любой код, использующий оператор канала (|>). Возьмите этот фрагмент из записи в Википедии на F # :

[1 .. 10]
|> List.map     fib

(* equivalent without the pipe operator *)
List.map fib [1 .. 10]

Функция map не является методом экземпляра списка. Вместо этого он работает как статический метод в модуле List, который принимает экземпляр списка в качестве одного из своих параметров.

Скала, с другой стороны, полностью ОО. Давайте начнем, во-первых, с Scala-эквивалента этого кода:

List(1 to 10) map fib

// Without operator notation or implicits:
List.apply(Predef.intWrapper(1).to(10)).map(fib)

Здесь map - это метод для экземпляра List. Статические методы, такие как intWrapper на Predef или apply на List, встречаются гораздо реже. Тогда есть функции, такие как fib выше. Здесь fib не является методом на int, но и не является статическим методом. Вместо этого это объект - второе основное отличие, которое я вижу между F # и Scala.

Давайте рассмотрим реализацию F # из Википедии и эквивалентную реализацию Scala:

// F#, from the wiki
let rec fib n =
    match n with
    | 0 | 1 -> n
    | _ -> fib (n - 1) + fib (n - 2)

// Scala equivalent
def fib(n: Int): Int = n match {
  case 0 | 1 => n
  case _ => fib(n - 1) + fib(n - 2)
}

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

// F#, returning a lambda, as suggested in the comments
let rec fib = function 
    | 0 | 1 as n -> n 
    | n -> fib (n - 1) + fib (n - 2)

// Scala method returning a function
def fib: Int => Int = {
  case n @ (0 | 1) => n
  case n => fib(n - 1) + fib(n - 2)
}

// Same thing without syntactic sugar:
def fib = new Function1[Int, Int] {
  def apply(param0: Int): Int = param0 match {
    case n @ (0 | 1) => n
    case n => fib.apply(n - 1) + fib.apply(n - 2)
  }
}

Итак, в Scala все функции являются объектами, реализующими черту FunctionX, которая определяет метод с именем apply. Как показано здесь и при создании списка выше, .apply может быть опущено, что делает вызовы функций похожими на вызовы методов.

В конце концов, все в Scala является объектом - и экземпляром класса - и каждый такой объект действительно принадлежит классу, а весь код принадлежит методу, который каким-то образом выполняется. Даже match в приведенном выше примере был методом, но был преобразован в ключевое слово, чтобы избежать некоторых проблем довольно давно.

Итак, как насчет его функциональной части? F # принадлежит к одному из самых традиционных семейств функциональных языков. Хотя у него нет некоторых функций, которые, по мнению некоторых людей, важны для функциональных языков, фактом является то, что F # является функцией, так сказать, по умолчанию .

Scala, с другой стороны, был создан с целью , объединяющей функциональные и ОО-модели, вместо того, чтобы просто предоставлять их как отдельные части языка. Степень успеха зависит от того, что вы считаете функциональным программированием. Вот некоторые вещи, на которых сосредоточился Мартин Одерский:

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

  • Сильная поддержка неизменяемых типов данных. Функциональное программирование всегда было направлено на уменьшение побочных эффектов на программу, чтобы функции можно было анализировать как истинные математические функции. Таким образом, Scala позволил сделать вещи неизменными, но он не сделал двух вещей, за которые критики FP критикуют это:

    • Это не делало изменчивость сложнее .
    • Он не предоставляет систему эффектов , с помощью которой можно статически отслеживать изменчивость.
  • Поддержка алгебраических типов данных. Алгебраические типы данных (называемые ADT, что также сбивает с толку абстрактный тип данных, совсем другое) очень распространены в функциональном программировании и наиболее полезны в ситуациях, когда обычно используют шаблон посетителя в языках OO.

    Как и все остальное, ADT в Scala реализованы как классы и методы, с некоторыми синтаксическими сахарами, которые делают их безболезненными для использования. Тем не менее, Scala гораздо более многословен, чем F # (или другие функциональные языки в этом отношении) в их поддержке. Например, вместо F # | для операторов case он использует case.

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

    Например, нестрогий список Scala, Stream, не поддерживает действительно строгий foldRight, как это делает Haskell. Более того, некоторые преимущества нестрогости достигаются только тогда, когда это значение по умолчанию в языке, а не опция.

  • Поддержка понимания списка. На самом деле, Scala называет это для понимания , поскольку способ его реализации полностью отделен от списков. В простейшем смысле, списочные выражения можно рассматривать как функцию / метод map, показанную в примере, хотя вложенность операторов карты (поддерживается с flatMap в Scala) и фильтрация (filter или withFilter) в Scala, в зависимости от требований строгости), как правило, ожидаются.

    Это очень распространенная операция в функциональных языках, и она часто отличается синтаксисом - как в операторе in в Python. Опять же, Scala более многословен, чем обычно.

На мой взгляд, Scala не имеет себе равных в сочетании FP и OO. Это происходит со стороны ОО спектра в сторону FP, что необычно. В основном, я вижу языки FP с ОО, которые рассматриваются на нем - и он чувствует себя решенным для меня. Я думаю, что FP на Scala, вероятно, чувствует себя так же для программистов на функциональных языках.

EDIT

Читая некоторые другие ответы, я понял, что есть еще одна важная тема: вывод типа. Лисп был динамически типизированным языком, и это в значительной степени устанавливало ожидания для функциональных языков. Все современные функциональные языки со статической типизацией имеют строгие системы логического вывода, чаще всего алгоритм Хиндли-Милнера 1 , который делает объявления типов по существу необязательными.

Scala не может использовать алгоритм Хиндли-Милнера из-за поддержки Scala наследования 2 . Поэтому Scala должен принять гораздо менее мощный алгоритм вывода типов - фактически вывод типов в Scala намеренно не определен в спецификации и является предметом постоянных улучшений (его усовершенствование является одной из важнейших функций в следующей версии 2.8). Скала, например).

В конце концов, однако, Scala требует, чтобы у всех параметров были объявлены их типы при определении методов. В некоторых ситуациях, таких как рекурсия, также должны быть объявлены типы возврата для методов.

Функции в Scala часто могут иметь свои типы выведенные вместо объявленных. Например, здесь не требуется объявления типа: List(1, 2, 3) reduceLeft (_ + _), где _ + _ на самом деле является анонимной функцией типа Function2[Int, Int, Int].

Аналогично, объявление типов переменных часто не нужно, но наследование может потребовать этого. Например, Some(2) и None имеют общий суперкласс Option, но на самом деле принадлежат разным подклассам. Поэтому обычно нужно объявить var o: Option[Int] = None, чтобы убедиться, что назначен правильный тип.

Эта ограниченная форма вывода типов намного лучше, чем обычно предлагают ОО-языки со статической типизацией, что дает Scala ощущение легкости, и намного хуже, чем обычно предлагают статически типизированные языки FP, что придает Scala ощущение тяжести. : -)

Примечания:

  1. На самом деле, алгоритм происходит от Дамаса и Милнера, который назвал его «Алгоритм W», согласно википедии .

  2. Мартин Одерский упомянул в комментарии здесь что:

    Причина, по которой у Скалы нет вывода типа Хиндли / Милнера, заключается в что это очень трудно сочетать с такими функциями, как перегрузка (специальный вариант, не типовые классы), запись выбор и подтип

    Он продолжает заявлять, что это не может быть на самом деле невозможным, и дело дошло до компромисса. Пожалуйста, перейдите по этой ссылке для получения дополнительной информации, и, если у вас есть более четкое заявление или, еще лучше, какой-либо документ, так или иначе, я был бы признателен за справку.

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

14 голосов
/ 25 мая 2010

F # является функциональным - он позволяет OO довольно хорошо, но дизайн и философия все же функциональны. Примеры:

  • Функции в стиле Haskell
  • Автоматическая подача
  • Автоматические генерики
  • Вывод типа для аргументов

Довольно неудобно использовать F # в основном объектно-ориентированным образом, поэтому можно описать основную цель интеграции ОО в функциональное программирование .

Scala - мультипарадигма с упором на гибкость. Вы можете выбрать между подлинным FP, ООП и процедурным стилем в зависимости от того, что в данный момент подходит лучше всего. Это действительно о объединении ОО и функционального программирования .

13 голосов
/ 05 июня 2010

Каковы основные различия между подходами, принятыми Scala и F # для унификации парадигм OO и FP?

Ключевое отличие состоит в том, что Scala пытается смешать парадигмы, жертвуя собой (обычно на стороне FP), тогда как F # (и OCaml) обычно проводят черту между парадигмами и позволяют программисту выбирать между ними для каждой задачи.

Скала должен был приносить жертвы, чтобы объединить парадигмы. Например:

  • Функции первого класса являются неотъемлемой особенностью любого функционального языка (ML, Scheme и Haskell). Все функции первого класса в F #. Функции-члены являются вторым классом в Scala.

  • Перегрузка и подтипы затрудняют вывод типов. F # предоставляет большой подъязык, который жертвует этими функциями OO, чтобы обеспечить мощный вывод типа, когда эти функции не используются (требуются аннотации типов, когда они используются). Scala повсеместно использует эти возможности для поддержания согласованного ОО за счет плохого вывода типов везде.

Другим следствием этого является то, что F # основан на проверенных и испытанных идеях, тогда как Scala является пионером в этом отношении. Это идеально подходит для мотивации проектов: F # - коммерческий продукт, а Scala - исследование языка программирования.

Кроме того, Scala также пожертвовала другими основными функциями FP, такими как оптимизация хвостового вызова, по прагматическим причинам из-за ограничений своей виртуальной машины (JVM). Это также делает Scala намного больше ООП, чем FP. Обратите внимание, что существует проект по переводу Scala на .NET, который будет использовать CLR для создания подлинной TCO.

Каковы относительные достоинства и недостатки каждого подхода? Если, несмотря на поддержку подтипов, F # может определять типы аргументов функций, то почему не может Scala?

Вывод типа не согласуется с ОО-ориентированными функциями, такими как перегрузка и подтипы. F # выбрал вывод типа вместо согласованности в отношении перегрузки. Scala выбрала повсеместную перегрузку и подтипы вместо вывода типа. Это делает F # больше похожим на OCaml и Scala больше похожим на C #. В частности, Scala не более функциональный язык программирования, чем C #.

Что лучше, конечно, полностью субъективно, но я лично предпочитаю огромную краткость и ясность, которые возникают из-за мощного вывода типа в общем случае. OCaml - замечательный язык, но одной из проблем было отсутствие перегрузки операторов, что требовало от программистов использования + для целых чисел, +. для чисел с плавающей запятой, +/ для рациональных чисел и так далее. Еще раз, F # выбирает прагматизм вместо одержимости, жертвуя выводом типа для перегрузки, особенно в контексте чисел, не только на арифметических операторах, но также и на арифметических функциях, таких как sin. Каждый угол языка F # является результатом тщательно подобранных прагматических компромиссов, подобных этому. Несмотря на возникающие несоответствия, я считаю, что это делает F # гораздо более полезным.

13 голосов
/ 26 мая 2010

Есть довольно много точек, которые вы можете использовать для сравнения двух (или трех). Во-первых, вот некоторые заметные моменты, о которых я могу подумать:

  • Синтаксис
    Синтаксически F # и OCaml основаны на традиции функционального программирования (разделены пробелами и более легковесны), в то время как Scala основана на объектно-ориентированном стиле (хотя Scala делает его более легким).

  • Интеграция OO и FP
    И F # , и Scala очень плавно интегрируют ОО с FP (потому что между этими двумя понятиями нет противоречий!). Вы можете объявить классы для хранения неизменяемых данных (функциональный аспект) и предоставить связанные члены для работы с данными вы также можете использовать интерфейсы для абстракции (объектно-ориентированные аспекты). Я не очень знаком с OCaml , но я думаю, что он делает больший акцент на стороне OO (по сравнению с F #)

  • Стиль программирования на F #
    Я думаю, что обычный стиль программирования, используемый в F # (если вам не нужно писать библиотеку .NET и у вас нет других ограничений), вероятно, более функциональный, и вы будете использовать функции OO только тогда, когда вы нужно. Это означает, что вы группируете функциональность, используя функции, модули и алгебраические типы данных.

  • Стиль программирования в Scala
    В Scala стиль программирования по умолчанию является более объектно-ориентированным (в организации), однако вы все еще (вероятно) пишете функциональные программы, потому что «стандартный» подход заключается в написании кода, который избегает мутаций.

7 голосов
/ 26 мая 2010

С эта статья о языках программирования:

Скала - бурная, выразительная, строго превосходная замена для Джава. Scala это программирование язык, который я бы использовал для такой задачи, как написание веб-сервера или IRC-клиента. В отличие от OCaml [или F #], который был функциональный язык с привитая к нему объектно-ориентированная система, Скала чувствует себя больше как настоящий гибрид объектно-ориентированного и функционального программирование. (то есть объектно-ориентированный программисты должны быть в состоянии начать используя Scala немедленно, поднимая функциональные части только так, как они выберите.)

Я впервые узнал о Scala в POPL 2006, когда Мартин Одерский дал пригласил поговорить об этом. В то время я видел функциональное программирование строго превосходит объектно-ориентированный программирование, поэтому я не видел необходимости для языка, который объединил функционал и объектно-ориентированное программирование. (Тот было, вероятно, потому что все, что я написал обратно тогда были компиляторы, интерпретаторы и статические анализаторы.)

Потребность в Скале не стала очевидно для меня, пока я не написал одновременный HTTPD с нуля Поддержка длинных опросов AJAX для яплетов. Для того, чтобы получить хороший многоядерный поддержку, я написал первую версию в Джава. Как язык, я не думаю, Ява все так плохо, и я могу наслаждаться хорошо выполненное объектно-ориентированное программирование. Как функциональный программист, однако, отсутствие (или излишне многословное) поддержка функционального программирования функции (например, функции высшего порядка) решает меня, когда я программирую на Java. Итак, я дал Скале шанс.

Scala работает на JVM, поэтому я мог постепенно портировать мой существующий проект в Скала. Это также означает, что Скала, в дополнение к его собственной довольно большой библиотека, имеет доступ ко всей Java библиотека также. Это означает, что вы можете получить реальную работу в Scala.

Когда я начал использовать Scala, я стал впечатлен тем, как хитро функциональные и объектно-ориентированные миры смешаны вместе. В частности, Скала имеет мощный случай система сопоставления классов / шаблонов, обратился к любимой мозоли задерживаясь от моего опыт работы со стандартом ML, OCaml и Haskell: программист может решить какие поля объекта должны быть сопоставимый (в отличие от принудительного чтобы соответствовать на всех из них), и Аргументы переменной арности разрешенный. На самом деле, Scala даже позволяет определенные программистом шаблоны. Я пишу много функций, которые работают на абстрактные синтаксические узлы, и это приятно чтобы иметь возможность совпадать только на синтаксические дети, но все же есть поля для таких вещей, как аннотации или строки в исходной программе. The система классов случая позволяет разделить определение алгебраического типа данных через несколько файлов или через несколько частей одного и того же файла, который удивительно удобно.

Скала также поддерживает четко определенные множественные наследование через классоподобные устройства называемые черты.

Scala также позволяет значительная степень перегрузки; даже приложение функции и массив обновление может быть перегружено. В моем опыт, это, как правило, делает мой Scala программы более интуитивно понятны и лаконичными.

Одна функция, которая оказывается, чтобы сохранить много кода, так же, как этот тип классы сохраняют код в Haskell, есть implicits. Вы можете представить себе последствия как API для фазы восстановления после ошибок тип проверки. Короче говоря, когда средство проверки типа нуждается в X, но получил Y, он проверит, есть ли неявная функция в области, которая преобразует Y в X; если он найдет один, он «бросает», используя неявное. Это делает можно выглядеть как ты распространяющийся практически на любой тип Скала, и это позволяет плотнее вложения DSL.

Из приведенного выше фрагмента ясно, что подход Scala к унификации парадигм OO и FP намного превосходит подход OCaml или F #.

HTH.

С уважением,
Эрик.

5 голосов
/ 25 мая 2010

Синтаксис F # был взят из OCaml, но объектная модель F # была взята из .NET. Это дает F # легкий и лаконичный синтаксис, характерный для функциональных языков программирования, и в то же время позволяет F # очень легко взаимодействовать с существующими языками .NET и библиотеками .NET через его объектную модель.

Scala выполняет ту же работу на JVM, что и F # на CLR. Однако Scala решила использовать более похожий на Java синтаксис. Это может помочь в его принятии объектно-ориентированными программистами, но функциональному программисту это может показаться немного тяжелым. Его объектная модель похожа на Java, позволяющую беспрепятственно взаимодействовать с Java, но имеет некоторые интересные отличия, такие как поддержка черт.

3 голосов
/ 26 мая 2010

Если функциональное программирование означает программирование с функциями , то Scala немного его изменит. В Scala, если я правильно понимаю, вы программируете с методами вместо функций.

Когда класс (и объект этого класса) позади метода не имеют значения, Scala позволит вам притвориться , что это просто функция. Возможно, адвокат языка Scala может уточнить это различие (если оно вообще является различием) и любые последствия.

...