Иван Кузьмицкий писал(а):
Справедливо ли проводить аналогию между имеющими состояние объектами и функциями?
А почему бы и нет? Развивая мысль Владимира о том, что процедуры можно рассматривать как объекты, можно сказать, что функция -- это объект-синглтон с одним-единственным методом и без полей.
Так же и объекты можно представлять как функции.
Честно говоря, какой-нибудь короткий и вместе с тем практичный пример мне сейчас в голову никак не лезет, вообще достоинства ООП- или любой другой технологии вряд ли можно на маленьких примерах показать.
Ну вот представим себе, что нам нужно создать объект-точку с методами доступа и изменения его внутреннего состояния.
Немного искусственный пример, на Хаскеле так обычно не делается, для этой задачи больше подошёл бы обычный АТД, но просто для демонстрации замыканий попробуем так:
Код:
-- Это описание типа этого "объекта" с перечислением его "методов"
-- В динамически типизируемых ФЯ можно было бы обойтись и без этого определения,
-- но Хаскелл капризничает: дай мне тип, иначе не могу вывести его сам
-- бесконечный цикл в типах образуется...
--
data (Num a) => Point a = Point { getX :: () -> a
, getY :: () -> a
, setX :: a -> Point a
, setY :: a -> Point a
, moveTo :: (a, a) -> Point a
, rMoveTo :: (a, a) -> Point a
}
-- Ну а это своего рода "фабрика объектов", реализующая эти "методы"
--
makePoint (x, y) = Point { getX = \() -> x
, getY = \() -> y
, setX = \newX -> makePoint (newX, y )
, setY = \newY -> makePoint ( x , newY)
, moveTo = \(newX, newY) -> makePoint (newX, newY)
, rMoveTo = \(dX, dY ) -> makePoint (x+dX, y+dY)
}
"Методы" являются замыканиями, так как эти функции захватывают в себе значения аргументов функции makePoint.
Теперь надо придумать пример использования этого "объекта". Чем отличаются объекты в ФП-реализации от обычных объектов из ООП, так это тем, что в ФП приходится явно заботиться о поддержании жизни в этих объектах. То есть каждый "метод" этого "объекта", изменяющие его "состояние", на самом деле возвращает копию этого "объекта" с какими-то изменёниями. И вот с этой копией придётся что-то делать, что бы дальше работать уже с ней, а оригинал забыть и отправить в утиль.
Код:
test1 = do
let p0 = makePoint (10, 20)
p1 = p0`moveTo` (30, 50)
p2 = p1`rMoveTo`(-10, 10)
print $ p2`getX`()
print $ p2`getY`()
Этот пример напечатает 20, а затем 60, т.е. сначала создали точку с координатами (10, 20), потом переместили её в координаты (30, 50), а затем сдвинули влево на 10 и вверх на 10. Текущие координаты -- (20, 60).
Тут налицо некоторый синтаксический оверхед по сравнению с ООП.
Попробуем подвигать эту точку в цикле:
Код:
test2 = do
let loop i n pt | i == n = pt
| otherwise = loop (i+1) n $ pt`rMoveTo`(1, -3)
p = loop 0 10 $ makePoint (10, 20)
print (p`getX`(), p`getY`())
Этот пример напечатает (20, -10), т.е. сначала создали точку с координатами (10, 20), а затем 10 раз сдвинули на вправо на 1 и вниз на 3. Текущие координаты -- (20, -10).
Можно сказать, что мы имеем вполне себе объект, у которого есть какие-то спрятанные внутренние поля (инкапсуляция), методы работы с этим объектом, только обращаться с ним с непривычки может быть не так удобно, как в ООП...
Можно даже сказать, что функция -- это синтаксический сахар для объекта, и в то же время объект -- синтаксический сахар для функции. Как любят говорить функциональщики и почему-то Илья Ермаков -- изоморфизм! Разница только в марке сахара... :о)