Как стать автором
Обновить

Комментарии 9

data Figure a = forall a. Paint a =>  Figure a

Мне кажется, или это должно быть
data Figure = forall a. Paint a => Figure a

т.е. у тайпконструктора Figure не должно быть параметра? Ведь он квантифицируется forall'ом, следовательно, это связанная типовая переменная внутри самого определения типа.
Вы правы. Поправил. Спасибо.
Спасибо за статью, как раз недавно разбирался немного с хаскеллом.
Пример слегка демотивирует — выглядит громоздко и нечитаемо по сравнению с ООП-версией.
Для этого я воспользуюсь расширением ExistentialQuantification, которое позволяет объединять вместе с данными

И получить антипаттерн.
А если вспомнить, что объекты – это замыкания для бедных, требуемую задачу можно изобразить без каких-либо языковых расширений:
import System.IO
import Text.Printf

data Figure = Figure
    { paint :: Handle -> IO ()
    , say :: String
    , circumSquare :: Int
    }

base child = child
    { paint = \handle -> hPutStrLn handle $ printf "paint %s    S=%d" (say child) (circumSquare child)
    }

type Point = (Int, Int)
data Rect = Rect {left, top, right, bottom :: Int}
    deriving Show

makeRect :: Point -> Point -> Rect
makeRect (left, top) (right, bottom) = Rect left top right bottom

circle :: Point -> Int -> Figure
circle (x, y) radius = base $ Figure
    { say = printf "circle, radius=%d and centre=(%d,%d)" radius x y
    , circumSquare = (2 * radius) ^ 2
    }

rect :: Point -> Point -> Figure
rect lt@(left, top) rt@(right, bottom) = base $ Figure
    { say = show $ makeRect lt rt
    , circumSquare = (right - left) * (bottom - top)
    }

roundrect :: Point -> Point -> Int -> Figure
roundrect lt rt roundR = (rect lt rt)
    { say = printf "round rectangle, %s and roundR = %d" (show $ makeRect lt rt) roundR
    }
Рад Вашему энтузиазму. (Без сарказма). Статью антипаттерн читал по ссылки с dev.stephendiehl.com/hask — это частное мнение, а не абсолют. То, что задачу можно решить разными способами, не сомневаюсь. И разные варианты имеют свои недостатки. Например, у Вас исходные координаты теряются, превращаясь сразу в строки. Конкретный пример решается, но, если потребуется координаты использовать разными способами, то станет посложнее (хотя, тоже решается). Я знаю и то что, в Haskell, вобщем то, не стОит применять ООП-шаблоны, а использовать Haskell-евские приёмы. Однако, есть тенденция — arxiv.org/pdf/cs/0509027v1.pdf
Кратко: у меня пример, демонстрирующий некоторые языковые расширения и использование классов типов. У Вас другой пример. Ничего не имею против. Поставлю лайк на Ваш ответ.
Что значит «теряются» (если они вон используются в нескольких местах), что значит «сразу» (особенно учитывая ленивую модель)?
То, что задачу можно решить разными способами, не сомневаюсь.

Это да. Однако обсуждения – они в первую очередь для «зрителей», и если забредший на функциональный огонёк неофит ужаснётся, сколько всего нужно накрутить, чтобы решить такую простую в ОО-языке задачу, то ему стоит увидеть и альтернативный подход.
Возможно, статье лучше бы подошло название вроде «Эмуляция традиционного ООП на языке Haskell».
Что значит «теряются» (если они вон используются в нескольких местах)

В смысле, что их не получить из списка [Figure], т.е. мы вынуждены реализовать все возможные действия как функции в Figure, т.е. объединяем хранение с логикой и представлением.
Возможно, статье лучше бы подошло название вроде «Эмуляция традиционного ООП на языке Haskell».

Мне кажется, что эмуляция — это, например, wiki.haskell.org/OOP_vs_type_classes, п.5. По классу типов на каждый тип данных.
То что у меня тоже сохраняются исходные данные и приходится использовать ExistentialQuantification — ну, не является оно абсолютным злом. По Вашей же ссылке: «Замыкания — это объекты для бедных!». Я, конечно, так не считаю, но название пока оставлю. Вставлю в начало статьи упоминание о приведённой ниже Вашей альтернативе.
В смысле, что их не получить из списка [Figure]

Так и в вашем коде не получить. Т.е. получить можно, но всё, что можно сделать — это позвать функции класса Paint, разве нет?
data Figure = forall x . (Typeable x, Paint x) => Figure x
Далее безопасно кастуете и выбираете наздоровье. В добавок можно ещё и красивые линзы прикрутить.
В целом использование явного словаря data D = D { ... } или невного класса-типов D это достаточно близкие методы, с небольшой разницей, вроде той, что в случае класса типов, для одного параметра у нас гарантируется наличие единственной реализации функций, чего нету в случае явного словаря. Так-же из функций класса типов мы можем достать сам словарь Dict (c::Constraint) и передавать его явно или восстановить его по прокси переменной. Все тоже самое возможно и с явным словарём, но с большим количеством страданий.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации