Я пришел из объектно-ориентированного фона, где я узнал, что классы используются или, по крайней мере, могут использоваться для создания уровня абстракции, который позволяет легко перерабатывать код, который затем может быть использован для создания объектов или для наследования.
Как, например, я могу иметь класс животных, а затем наследовать кошек и собак и так, чтобы все унаследовали многие одинаковые черты, и из этих подклассов я могу затем создавать объекты, которые могут указывать породу животного или даже название этого
Или я могу использовать классы для указания нескольких экземпляров одного и того же кода, который обрабатывает или содержит несколько разные вещи; как узлы в дереве поиска или несколько разных соединений с базой данных, а что нет.
Недавно я перешел к функциональному программированию, поэтому я начал задумываться:
как чисто функциональные языки справляются с такими вещами? То есть языки без какой-либо концепции классов и объектов.
источник
Ответы:
Многие функциональные языки имеют модульную систему. (Кстати, многие объектно-ориентированные языки тоже.) Но даже при отсутствии такового вы можете использовать функции в качестве модулей.
JavaScript является хорошим примером. В JavaScript функции используются как для реализации модулей, так и даже для объектно-ориентированной инкапсуляции. В Scheme, которая была основным источником вдохновения для JavaScript, есть только функции. Функции используются для реализации практически всего: объектов, модулей (называемых модулями в Racket), даже структур данных.
OTOH, Haskell и семейство ML имеют явную модульную систему.
Ориентация на объект - это абстракция данных. Вот и все. Модульность, Наследование, Полиморфизм, даже изменяемое состояние являются ортогональными проблемами.
источник
module
. Я думаю, к сожалению, у Racket есть концепция,module
которая называется не модулем, и концепция, которая является модулем, но не называетсяmodule
. Как бы то ни было, вы написали: «модули - это нечто среднее между пространствами имен и интерфейсами OO». Ну, разве это не такое определение модуля?map
и модуль, зависящий от привязкиmap
, отличаются тем, что модуль должен ссылаться на некоторую конкретнуюmap
привязку, например, отracket/base
, в то время как разные пользователи блока могут давать разные определенияmap
для блока.Похоже, вы задаете два вопроса: «Как достичь модульности в функциональных языках?» о чем говорилось в других ответах и "как вы можете создавать абстракции в функциональных языках?" на что я отвечу.
В ОО-языках вы склонны концентрироваться на существительном, «животном», «почтовом сервере», «его садовой вилке» и т. Д. Функциональные языки, напротив, подчеркивают глагол «ходить», «получать почту» , "подталкивать" и т. д.
Поэтому неудивительно, что абстракции в функциональных языках имеют тенденцию быть над глаголами или операциями, а не над вещами. Один пример, к которому я всегда прибегаю, когда пытаюсь объяснить это, - анализ. На функциональных языках хорошим способом написания синтаксических анализаторов является указание грамматики и ее интерпретация. Интерпретатор создает абстракцию над процессом разбора.
Другим конкретным примером этого является проект, над которым я работал не так давно. Я писал базу данных на Хаскеле. У меня был один «встроенный язык» для определения операций на самом низком уровне; например, это позволило мне писать и читать вещи с носителя. У меня был другой, отдельный «встроенный язык» для определения операций на самом высоком уровне. Тогда у меня был, по сути, переводчик, для преобразования операций с более высокого уровня на более низкий уровень.
Это удивительно общая форма абстракции, но она не единственная, доступная на функциональных языках.
источник
Хотя «функциональное программирование» не дает далеко идущих последствий для проблем модульности, определенные языки решают проблему программирования в целом по-разному. Повторное использование кода и абстракция взаимодействуют в том смысле, что чем меньше вы выставляете, тем сложнее повторно использовать код. Отложив абстракцию, я рассмотрю два вопроса повторного использования.
В статически типизированных языках ООП традиционно использовались номинальные подтипы, что означает, что код, разработанный для класса / модуля / интерфейса A, может иметь дело только с классом / модулем / интерфейсом B, когда B явно упоминает A. В языках семейства функционального программирования в основном используется структурный подтип, что означает этот код, разработанный для A, может обрабатывать B всякий раз, когда B имеет все методы и / или поля из A. B мог быть создан другой командой, прежде чем возникла необходимость в более общем классе / интерфейсе A. Например, в OCaml, структурный подтип применяется к модульной системе, ООП-подобной объектной системе и ее совершенно уникальным полиморфным вариантным типам.
Наиболее заметная разница между ООП и ФП по сравнению с Модульность заключается в том, что «единица» по умолчанию в ООП связывает воедино как объект различные операции над одним и тем же регистром значений, тогда как «единица» по умолчанию в ФП связывает воедино как функция одна и та же операция для различных случаев значений. В FP все еще очень легко объединять операции, например, в виде модулей. (Кстати, ни у Haskell, ни у F # нет полноценной системы модулей семейства ML.) Проблема выраженияэто задача постепенного добавления как новых операций, работающих со всеми значениями (например, присоединение нового метода к существующим объектам), так и новых случаев значений, которые должны поддерживать все операции (например, добавление нового класса с тем же интерфейсом). Как обсуждалось в первой лекции Ральфа Леммела ниже (которая содержит обширные примеры на C #), добавление новых операций проблематично в языках ООП.
Сочетание ООП и FP в Scala может сделать его одним из самых мощных языков. модульность. Но OCaml по-прежнему мой любимый язык, и, по моему личному, субъективному мнению, он не уступает Scala. В двух лекциях Ральфа Леммела ниже обсуждается решение проблемы выражения в Хаскеле. Я думаю, что это решение, хотя и прекрасно работает, затрудняет использование полученных данных с параметрическим полиморфизмом. Решение проблемы выражения с полиморфными вариантами в OCaml, объясненное в статье Jaques Garrigue, приведенной ниже, не имеет этого недостатка. Я также ссылаюсь на главы из учебников, в которых сравнивается использование не-ООП и ООП-модульности в OCaml.
Ниже приводятся ссылки, специфичные для Haskell и OCaml, на проблему выражения :
источник
На самом деле, ОО-код гораздо менее пригоден для повторного использования, и это дизайн. Идея ООП заключается в том, чтобы ограничить операции над отдельными частями данных определенным привилегированным кодом, который находится либо в классе, либо в соответствующем месте в иерархии наследования. Это ограничивает неблагоприятные последствия изменчивости. Если структура данных изменяется, в коде может быть только столько мест, которые могут быть ответственными.
С неизменяемостью вам все равно, кто может работать с любой данной структурой данных, потому что никто не может изменить вашу копию данных. Это значительно облегчает создание новых функций для работы с существующими структурами данных. Вы просто создаете функции и группируете их в модули, которые кажутся подходящими с точки зрения предметной области. Вам не нужно беспокоиться о том, где вписать их в иерархию наследования.
Другой вид повторного использования кода - создание новых структур данных для работы с существующими функциями. Это обрабатывается на функциональных языках с использованием таких функций, как обобщенные типы и классы типов. Например, класс типов Ord в Haskell позволяет использовать
sort
функцию для любого типа сOrd
экземпляром. Экземпляры легко создавать, если они еще не существуют.Возьмите свой
Animal
пример и подумайте о реализации функции кормления. Простая реализация ООП состоит в том, чтобы поддерживать коллекциюAnimal
объектов и перебирать все их, вызываяfeed
метод для каждого из них.Однако, когда дело доходит до деталей, все становится сложнее.
Animal
Объект естественно знает , какую пищу он ест, и сколько это необходимо для того , чтобы чувствовать себя полностью. Естественно, он не знает, где хранится еда и сколько ее имеется, поэтомуFoodStore
объект только что стал зависимым от каждогоAnimal
, либо как полеAnimal
объекта, либо передан в качестве параметраfeed
метода. С другой стороны, чтобы сохранитьAnimal
класс более сплоченным, вы можете перейтиfeed(animal)
кFoodStore
объекту, или вы можете создать мерзость класса, называемогоAnimalFeeder
или что-то подобное.В FP нет склонности к тому, чтобы поля
Animal
всегда оставались сгруппированными, что имеет некоторые интересные последствия для повторного использования. Скажем , у вас есть списокAnimal
записей, с такими областями , какname
,species
,location
,food type
,food amount
и т.д. У вас также есть списокFoodStore
записей с полями , какlocation
,food type
иfood amount
.Первым шагом в кормлении может быть сопоставление каждого из этих списков записей со списками
(food amount, food type)
пар с отрицательными числами для количества животных. Затем вы можете создавать функции для выполнения всех этих действий с этими парами, например, суммировать количество продуктов каждого типа. Эти функции не принадлежат ни к одному,Animal
ни кFoodStore
модулю, но их можно использовать многократно.В итоге вы получаете кучу функций, которые делают полезные вещи,
[(Num A, Eq B)]
которые можно использовать многократно и модульно, но у вас не получается понять, где их разместить или как их называть группой. В результате модули FP сложнее классифицировать, но классификация гораздо менее важна.источник
Одно из популярных решений - разбить код на модули, вот как это делается в JavaScript:
Статья полностью объяснить эту модель в JavaScript , кроме этого есть множество других способов определить модуль, например RequireJS , CommonJS , Google Closure. Другим примером является Erlang, где у вас есть как модули, так и поведения, которые обеспечивают API и шаблон, играющие роль, аналогичную интерфейсам в ООП.
источник