Борьба с использованием чистого функционального программирования для решения повседневных задач

Я видел этот пост в хакерские новости сегодня. Я борюсь с теми же проблемами понимания того, как чистое функциональное программирование поможет мне абстрагироваться от реальной проблемы. Я перешел с императивного на объектно-ориентированное программирование 7 лет назад. Я чувствую, что освоил его, и он сослужил мне хорошую службу. За последние пару лет я изучил некоторые приемы и концепции функционального программирования, такие как map и reduce, и они мне тоже нравятся. Я использовал их в своем объектно-ориентированном коде и был доволен этим, но при абстрагировании набора инструкций я могу думать об объектно-ориентированных абстракциях только для того, чтобы сделать код красивее.

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

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

В OO я, вероятно, создал бы базовый класс SymlinkImage, подкласс ThumbnailSymlinkImage и подкласс OriginalSymlinkImage. Общие данные (в классе SymlinkImage) будут такими, как путь к оригиналу. Общее поведение будет создавать символическую ссылку. Подклассы будут реализовывать метод, называемый чем-то вроде «генерировать», который будет отвечать за создание миниатюры, если это применимо, и вызов своего суперкласса для создания новой символической ссылки.


person Frank Henard    schedule 31.05.2011    source источник


Ответы (3)


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

Вот скетч с использованием типизированного, по умолчанию чистого, функционального языка программирования Haskell. Мы создаем новые типы для ключевых понятий вашей проблемы и разбиваем работу на отдельные функции, выполняющие одну задачу за раз. IO и другие побочные эффекты (например, создание символической ссылки) ограничены определенными функциями и обозначены типом. Чтобы различать два режима работы, мы используем тип суммы.

--
-- User can request an image or a thumbnail of the image.
-- If the user requests the thumbnail of the image, and it doesn't yet exist, create it using
-- python's PIL module. Also create a symbolic link to the original or
-- thumbnail with a human readable path, because the original image name is a
-- hashcode, and not descriptive of it's contents. Finally, redirect to the
-- symbolic link of that image.
--

module ImageEvent where

import System.FilePath
import System.Posix.Files

-- Request types
data ImgRequest = Thumb ImgName | Full ImgName

-- Hash of image 
type ImgName = String

-- Type of redirects
data Redirect

request :: ImgRequest -> IO Redirect
request (Thumb img) = do
    f <- createThumbnail img
    let f' = normalizePath f
    createSymbolicLink f f'
    return (urlOf f)

request (Full img)  = do
    createSymbolicLink f f'
    return (urlOf f)
    where
        f  = lookupPath img
        f' = normalizePath f

Наряду с некоторыми помощниками, определение которых я оставлю на ваше усмотрение.

-- Creates a thumbnail for a given image at a path, returns new filepath
createThumbnail :: ImgName -> IO FilePath
createThumbnail f = undefined
    where
        p = lookupPath f

-- Create absolute path from image hash
lookupPath :: ImgName -> FilePath
lookupPath f = "/path/to/img" </> f <.> "png"

-- Given an image, construct a redirect to that image url
urlOf :: FilePath -> Redirect
urlOf = undefined

-- Compute human-readable path from has
normalizePath :: FilePath -> FilePath
normalizePath = undefined

По-настоящему красивым решением было бы абстрагировать модель запроса/ответа структурой данных, представляющей последовательность выполняемых команд. Приходит запрос, создается структура исключительно для представления того, какую работу необходимо выполнить, и она передается механизму выполнения, который выполняет такие действия, как создание файлов и так далее. Тогда основная логика будет полностью чистыми функциями (не то чтобы в этой задаче было много основной логики). В качестве примера такого стиля действительно чисто функционального программирования с эффектами я рекомендую статью Воутера Свистры, ``Красота в чудовище: функциональная семантика для отряда неуклюжих''

person Don Stewart    schedule 31.05.2011
comment
Выбранный вами подход является более или менее процедурным; не сказать, что это плохо или что-то в этом роде. О, и спасибо за ссылку! - person Artyom Shalkhakov; 01.06.2011

Лично я думаю, что проблема в том, что вы пытаетесь использовать функциональное программирование для решения проблем, которые разработаны/установлены для императивного программирования. 3 популярные парадигмы (функциональная, императивная, объектно-ориентированная) имеют разные сильные стороны:

  • Функциональное программирование делает акцент на описании того, ЧТО нужно сделать, обычно с точки зрения ввода/результата.
  • Императивное программирование делает акцент на том, КАК что-то делать, обычно с точки зрения списка и порядка шагов, которые нужно предпринять, и состояний, которые нужно изменить.
  • Объектно-ориентированное программирование делает акцент на ОТНОШЕНИЯХ между сущностями в системе.

Таким образом, когда вы подходите к проблеме, в первую очередь нужно перефразировать ее так, чтобы предполагаемая парадигма могла ее правильно решить. Кстати, в качестве побочного узла, насколько мне известно, не существует такого понятия, как "чистое ООП". Код в методах ваших ООП-классов (будь то Java, C#, C++, Python или Objective C) императивен.

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

Чтобы переформулировать проблему:

  • Входные данные: тип изображения (полное или миниатюрное), имя изображения, файловая система.
  • Вывод: запрошенный образ, файловая система с запрошенным образом

Из новой постановки задачи вы можете решить ее следующим образом:

def requestImage(type, name, fs) : 
    if type == "full" :
        return lookupImage(name, fs), fs
    else:
        thumb = lookupThumb(name, fs)
        if(thumb) :
            return thumb, fs
        else:
            thumb = createThumbnail(lookupImage(name, fs))
            return thumb, addThumbnailToFs(fs, name, thumb)

Конечно, это неполный вариант, но мы всегда можем рекурсивно решать задачи lookupImage, lookupThumb, createThumbnail и addThumbnailToF примерно таким же образом.

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

person magice    schedule 31.05.2011
comment
построение функционального решения почти невозможно - я только что прямо написал одно... разложение проблемы на составные компоненты, использование типов и строгое разделение задач - это не магия или уловки. Они инженерные. - person Don Stewart; 31.05.2011
comment
Лично я не считаю побочный эффект (в ML, семействе LISP и Erlang) и монаду (в Haskell) настоящим функциональным программированием. Первое очевидно; последний представляет собой более или менее список операций (изменяющих состояние), которые необходимо выполнить, поэтому на самом деле это не композиция результатов. - person magice; 31.05.2011
comment
Мне помогает думать о функциональном программировании с точки зрения преобразования данных. Я обнаружил, что использую FP так же, как раньше использовал Perl, когда я взял его из книги о розовом верблюде. - person Albert Perrien; 31.05.2011
comment
@magice Монады не настоящее программирование Монады - ваша альтернатива объектно-ориентированному программированию. Там большая часть ФП. Также существует множество функциональных и объектно-ориентированных языков. ОО не имеет ничего общего с функциональным/императивным разделением, оно ортогонально. То, что у вас есть ОО/классы, не означает, что вы должны использовать их императивным образом. - person Raynos; 01.06.2011
comment
+1 @Райнос. Существует поток StackOverflow, в котором подробно обсуждается ложная дихотомия OO-FP. - person missingfaktor; 01.06.2011
comment
Меня шокирует то, как люди думают, что ООП и ФП — разные парадигмы. Как будто люди больше не изучают CLISP. Это в значительной степени мета-OO, все прорывы в OO были сделаны в LISP. - person Raynos; 01.06.2011
comment
Есть много языков, которые обеспечивают ОБА OO и FP. Scala, OCaml, F# и многие другие. - person missingfaktor; 01.06.2011

Единственный способ изменить свой образ мышления — это изменить свой образ мышления. Я могу сказать вам, что сработало для меня:

Я хотел работать над личным проектом, который требовал параллелизма. Я осмотрелся и нашел erlang. Я выбрал его, потому что считал, что он обеспечивает наилучшую поддержку параллелизма, а не по какой-либо другой причине. Я никогда раньше не работал с функциональными языками (просто для сравнения, я начал заниматься объектно-ориентированным программированием в начале 1990-х).

Я читал книгу Армстронга об эрланге. Это было тяжело. У меня был небольшой проект, над которым нужно было поработать, но я продолжал над ним работать.

Проект потерпел неудачу, но через пару месяцев я достаточно наметил все в своей голове, чтобы больше не думать объектами, как раньше.

Я прошел этап, когда я сопоставлял объекты с процессами erlang, но это было не слишком долго, и я вышел из него.

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

Я думаю, что работа на Python может вас сдерживать, и я настоятельно рекомендую вам проверить erlang. Его синтаксис очень чужд, но это тоже хорошо, так как более традиционный синтаксис (по крайней мере, для меня) привел бы к попыткам запрограммировать его по-старому и разочарованию.

person Agent Smith-Jones    schedule 31.05.2011
comment
Чтобы расширить рекомендацию по erlang: Erlang, Haskell, LISP, ML, F# и т. д. — все это хорошие надежные языки для программирования в функциональном стиле. Гибриды вроде C#/python/ruby/js/php дают слишком много возможностей, чтобы вернуться к императивным методам. - person Raynos; 01.06.2011