Создать тему  Создать ответ 
Common Lisp vs. Haskell: типы
18-03-2014, 23:20    
Сообщение: #1
Quasus

Гоф-фурьер
Сообщений: 625
Зарегистрирован: 17.06.12

Common Lisp vs. Haskell: типы
Есть интерес сравнить CLOS и типы в Haskell и попутно в чём-нибудь из этого разобраться.

В Common Lisp есть большой набор стандартных типов, простых и составных (например, числа, буквы, символы; конс-ячейки, массивы, хеш-таблицы). Также программист может создавать свои типы. Во-первых, типом может стать любое множество значений неважно каких типов; типы можно строить по логическим предикатам. Во-вторых, можно конструировать принципиально новые типы. В простейшем случае это структуры, аналогичные паскалевским записям - составной тип с именованными полями. В более сложном случае это классы. Классы также имеют именованные поля, но вообще там всё сложно. Гм.

ООП с точки зрения лиспа - это генеричные функции (generic function). В лиспе методы привязаны не к классам, а к генеричным функциям. Генеричная функция - это по сути набор конкретных функций (методов) с одним и тем же именем, однако принимающих аргументы разных типов. Когда генеричная функция принимает аргументы, она выбирает наиболее специфичный метод для их типов и применяет его.

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

Подозреваю, что это могло бы иметь параллели в хаскеле (которого не знаю). Ну или по крайней мере можно сравнить. Здесь типы - и в хаскеле типы...
Найти все сообщения
Цитировать это сообщение
19-03-2014, 00:58    
Сообщение: #2
arseniiv

± ∓
Сообщений: 227
Зарегистрирован: 05.07.12

 
Ага. В хаскеле типы нельзя создавать как подмножества значений, ограниченные предикатом или своевольным перечислением, зато их можно создавать как вложенные друг в друга записи и объединения (некоторые ограничения на множество значений можно сделать с помощью generic algebraic data types, GADT, но не такие, чтобы прям как ограничение по предикату).

Но значение может иметь и не один тип, а некоторое множество, соответствующее ограничениям. Ограничения сначала включали в себя только указания классов, экземплярами которых должны быть типы. Класс — это, грубо говоря, отношение на типах. Класс от одного параметра можно понимать как свойство, которым тип может обладать или нет, класс от двух параметров — бинарное отношение: например, на типах массивов и типах их индексов.

И вот в первую очередь классы дают что-то похожее на CLOS’овы generic functions: чтобы тип или кортеж типов стали экземпляром класса, надо написать, как для этих типов определяются методы класса, ну это просто функции, которые описаны в классе, которые могут кроме обычных типов включать в свой тип и типы параметров класса. Когда такая функция где-то вычисляется, компилятор смотрит, есть ли соответствующий экземпляр для класса, в котором она описана, и заменяет её на найденное определение. Отсюда полиморфизм таких функций. Классы позволяют связать несколько таких функций вместе не просто описанием и вынужденностью определения в одном месте, но ещё и возможностью определить одну через другую, или даже все через все каким-то способом. Это будут определения по умолчанию, если описывальщики экземпляра не смогут предложить ничего лучше (в справке при этом опишут minimal complete definition в виде нескольких подмножеств функций — либо эти три определи, либо те две).

Может быть ещё полиморфизм типов с помощью семейств типов, но это уже не стандартный хаскель, хотя я бы из-за этого не переживал. И, точно не помню, может быть ещё какой-нибудь полиморфизм функций, не в виде методов класса.

Продолжу прерванные классы примером: пусть нам нужна генеричная функция, которая сериализует разные вещи в строки. И её обратная подруга. Можно написать что-то такое:

class Serializable a where
  serialize :: a -> String
  deserialize :: String -> Maybe a


(Maybe a я написал на случай, если строка не представляет никакого полезного значения.)

После этого можно определить несколько экземпляров (я не буду напрягаться). Единичный тип () содержит одно значение () (это омонимы).

instance Serializable () where
  serialize () = "()"
  deserialize "()" = Just ()
  deserialize _ = Nothing


instance Serializable Bool where
  serialize True = "True"
  serialize False = "False"
  deserialize "True" = Just True
  deserialize "False" = Just False
  deserialize _ = Nothing


И так можно наописывать конкретных типов экземплярами этого класса, и serialize "12" сработает по-одному, а serialize False по-второму. И можно написать что-то такое:

printSerialized :: Serializable a => a -> IO ()
printSerialized = print . serialize -- внезапно!


Эта функция тоже получается генеричная, и это отражено в её типе: применима ко всем типам a, про которые известно, что Serializable a. У самих методов Serializable похожие типы:

serialize :: Serializable a => a -> String
deserialize :: Serializable a => String -> a


Надо, кстати заметить, что последняя может возвращать разные значения, принимая одни только строки. Как она узнает, что возвращать? Или из контекста, когда тип результата известен, или из чего-то типа deserialize "123" :: Double — сами тип укажем.

А что если нам нужно наследование? Оно есть, так как можно указать ограничения не только в типах, но и в описании класса или экземпляра. Как-то так:

-- класс
class Durak a where
  speciality :: a -> Speciality

-- подкласс, дополнительная функциональность
class Durak a => KruglyDurak a where
  kruglost :: a -> Double

-- с помощью ограничений можно объявить сразу кучу экземпляров
-- по круглости дураки сравнимы, вот и опишем сравнимость:
instance KruglyDurak a => Ord a where
  compare a b = compare (kruglost a) (kruglost b)


вместо Durak a могло бы быть несколько ограничений на a. А если у нас класс с несколькими параметрами, ограничения могли бы быть на оба, по одному или в любых комбинациях.

Описать экземпляр «подкласса» (здесь KruglyDurak a) не получится без описания для того же списка параметров (здесь один a) экземпляров всех ограничений (Durak a), и можно использовать в определениях его методов методы, которые нам дарят ограничения. Тем и наследование.

Тут отличие в том, что никакого наследования типов (как в CLOS) не допускается. Даже в семествах типов нет наследования — там есть только спецификация: S Int пусть так хранится, а S Bool по-другому. Хотя и с самими классами о наследовании не всегда удобно говорить, потому что вместо дерева имеется граф, и не просто граф, а с дополнительной структурой (надо же различать, какие списки параметров идут в какие ограничения).

Следующая итерация?

Honor thy error as a hidden intention
Вебсайт Найти все сообщения
Цитировать это сообщение
Создать ответ 


Переход:


Пользователи просматривают эту тему: 1 Гость(ей)