Я - разработчик C#. Происходя из стороны OO мира, я запускаю с размышления с точки зрения интерфейсов, классов и ввожу иерархии. Из-за отсутствия OO в Haskell иногда я нахожу меня застрявшим, и я не могу думать о способе смоделировать определенные проблемы с Haskell.
Как смоделировать, в Haskell, ситуации с реальным миром, включающие иерархии классов такой как один показанный здесь: http://www.braindelay.com/danielbray/endangered-object-oriented-programming/isHierarchy-4.gif
Предположим, что следующие операции: люди могут говорить, собаки могут лаять, и все представители вида могут спариваться с представителями того же вида, если они имеют противоположный пол. Я бы определил это в haskell так:
data Gender = Male | Female deriving Eq
class Species s where
gender :: s -> Gender
-- Returns true if s1 and s2 can conceive offspring
matable :: Species a => a -> a -> Bool
matable s1 s2 = gender s1 /= gender s2
data Human = Man | Woman
data Canine = Dog | Bitch
instance Species Human where
gender Man = Male
gender Woman = Female
instance Species Canine where
gender Dog = Male
gender Bitch = Female
bark Dog = "woof"
bark Bitch = "wow"
speak Man s = "The man says " ++ s
speak Woman s = "The woman says " ++ s
Теперь операция matable
имеет тип Species s => s -> s -> Bool
, bark
имеет type Canine -> String
и speak
имеет тип Human -> String -> String
.
Не знаю, помогает ли это, но, учитывая довольно абстрактный характер вопроса, это лучшее, что я мог придумать.
Редактировать: В ответ на комментарий Даниэля:
Простая иерархия для коллекций может выглядеть так (игнорируя уже существующие классы, такие как Foldable и Functor):
class Foldable f where
fold :: (a -> b -> a) -> a -> f b -> a
class Foldable m => Collection m where
cmap :: (a -> b) -> m a -> m b
cfilter :: (a -> Bool) -> m a -> m a
class Indexable i where
atIndex :: i a -> Int -> a
instance Foldable [] where
fold = foldl
instance Collection [] where
cmap = map
cfilter = filter
instance Indexable [] where
atIndex = (!!)
sumOfEvenElements :: (Integral a, Collection c) => c a -> a
sumOfEvenElements c = fold (+) 0 (cfilter even c)
Теперь sumOfEvenElements принимает любую коллекцию интегралов и возвращает сумму всех четных элементов этой коллекции.
Вместо классов и объектов Haskell использует абстрактные типы данных . Это действительно два совместимых взгляда на проблему организации способов построения и наблюдения информации. Лучшее пособие, которое я знаю по этому поводу, - это эссе Уильяма Кука Объектно-ориентированное программирование против абстрактных типов данных . У него есть несколько очень четких объяснений того, что
В системе, основанной на классах, код организован вокруг различных способов построения абстракций. Обычно каждому способу построения абстракции присваивается собственный класс. Методы умеют наблюдать только за свойствами этой конструкции.
В системе на основе ADT (например, Haskell) код организован вокруг различных способов наблюдения абстракций. Обычно каждому способу наблюдения за абстракцией назначается своя функция. Функция знает все способы построения абстракции, и она знает, как наблюдать отдельное свойство, но любую конструкцию.
Статья Кука покажет вам красивую матричную компоновку абстракций и научит, как организовать любой класс как ADY или наоборот.
Иерархии классов включают еще один элемент: повторное использование реализаций посредством наследования.В Haskell такое повторное использование вместо этого достигается с помощью функций первого класса: функция в абстракции Primate
является значением, а реализация абстракции Human
может повторно использовать любые функции из ] Primate
, может обернуть их, чтобы изменить их результаты, и так далее.
Нет точного соответствия между дизайном с иерархиями классов и дизайном с абстрактными типами данных. Если вы попытаетесь выполнить транслитерацию с одного на другой, вы получите что-то неудобное и не идиоматическое - что-то вроде программы FORTRAN, написанной на Java. Но если вы понимаете принципы иерархий классов и принципы абстрактных типов данных, вы можете найти решение проблемы в одном стиле и разработать разумно идиоматическое решение той же проблемы в другом стиле. Это требует практики.
Дополнение: также можно использовать систему типов и классов Haskell, чтобы попытаться имитировать иерархию классов, но это уже совсем другое дело. Классы типов достаточно похожи на обычные классы, чтобы работать со многими стандартными примерами, но они достаточно разные, так что могут быть очень большие сюрпризы и несоответствия. Хотя классы типов являются бесценным инструментом для программиста на Haskell, я бы рекомендовал всем, кто изучает Haskell, научиться разрабатывать программы с использованием абстрактных типов данных.
Прежде всего: стандартный объектно-ориентированный дизайн не будет хорошо работать в Haskell. Вы можете бороться с языком и попытаться создать что-то подобное, но это будет упражнением в разочаровании. Итак, первый шаг - это поиск решений вашей проблемы в стиле Haskell вместо поиска способов написания решения в стиле ООП на Haskell .
Но это легче сказать, чем сделать! С чего начать?
Итак, давайте разберем мельчайшие детали того, что ООП делает для нас, и подумаем, как это может выглядеть в Haskell.
защищенными
или внутренними
членами. map
с перевернутыми аргументами. Если вы примените его к списку Int
s, вы получите функцию типа (Int -> b) -> [b]
.Список, который вы ему дали, в некотором смысле все еще «там», но ничто другое не может использовать его, кроме как через функцию. Это сравнимо с частными
членами, а частично применяемая исходная функция сравнима с конструктором в стиле ООП. свертки
, которые обобщают почти все итерационные циклы, преобразования списков и линейно рекурсивные функции. sort
имеет тип (Ord a) => [a] -> [a]
; он полностью отделен от деталей типа, который вы ему даете, кроме того, что это должен быть список какого-то типа, реализующего Ord
.Вы также можете найти эту запись в блоге полезной; он дает краткое изложение того, что вы бы использовали в Haskell для решения тех же проблем, для решения которых часто используются некоторые стандартные шаблоны проектирования в ООП.
В качестве последнего добавления, как программист на C #, вам может быть интересно исследовать связи между ним и Haskell. Немало людей, ответственных за C #, также являются программистами на Haskell, и некоторые недавние дополнения к C # были во многом основаны на Haskell. Наиболее примечательной является, вероятно, монадическая структура, лежащая в основе LINQ, при этом IEnumerable по сути является монадой списка.