Scala по сравнению с вопросом F #: как они объединяют парадигмы OO и FP?

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

ИЗМЕНИТЬ

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

Ответы

Ответ 1

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

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

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

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

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

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

  • Функции - это значения. Они также являются объектами, потому что все значения являются объектами в 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, в зависимости от требований строгости) обычно ожидаются.

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

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

ИЗМЕНИТЬ

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

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

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

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

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

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

Примечания:

  • На самом деле алгоритм берет свое начало от Дамаса и Милнера, которые назвали его "Алгоритмом W", согласно wikipedia.

  • Мартин Одерский упоминает в комментарии здесь, что:

    Причина Scala не имеет вывода типа Хиндли/Милнера что очень сложно сочетать такие функции, как перегрузка (вариант ad-hoc, а не классы классов), запись выбор и подтипирование

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

Scala должен был принести жертвы, чтобы унифицировать парадигмы. Например:

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

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

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

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

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

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

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

Ответ 5

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

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

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

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

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

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

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

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

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

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

НТН.

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

Ответ 6

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

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

Ответ 7

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

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