список отображения различных типов, реализующих одну и ту же функцию? - PullRequest
3 голосов
/ 17 июня 2010

Я хочу применить функцию к каждому элементу в списке (карте), но элементы могут иметь разные типы, но все они реализуют одну и ту же функцию (здесь "putOut"), как интерфейс. Однако я не могу создать список этого типа "интерфейс" (здесь "Outputable").

Как мне отобразить список разных типов, реализующих одну и ту же функцию?

import Control.Monad

main :: IO ()
main = do
 mapM_ putOut lst
 where
  lst :: [Outputable] -- ERROR: Class "Outputable" used as a type
  lst = [(Out1 1),(Out2 1 2)]

class Outputable a where
 putOut :: a -> IO ()

-- user defined:

data Out1 = Out1 Int deriving (Show)
data Out2 = Out2 Int Int deriving (Show)

instance Outputable Out1 where
 putOut out1 = putStrLn $ show out1

instance Outputable Out2 where
 putOut out2 = putStrLn $ show out2

Ответы [ 2 ]

10 голосов
/ 17 июня 2010

Haskell не допускает разнородных списков. Таким образом, вы не можете составить список Outputables, потому что ваши Out1 и Out2 - это два разных типа, даже если они оба принадлежат к одному классу типов.

Но есть обходной путь, который позволяет имитировать гетерогенные списки с ExistentialQuantification. См. Пример гетерогенных списков в викибуке на Haskell.

Как использовать

  1. Поместите {-# LANGUAGE ExistentialQuantification #-} в верхней части модуля

  2. Определите тип ящика, который скрывает гетерогенные элементы внутри:

      data ShowBox = forall s. Show s => SB s
      heteroList :: [ShowBox]
      heteroList = [SB (), SB 5, SB True]
    
  3. Определите необходимый экземпляр класса для самого типа блока:

      instance Show ShowBox where
        show (SB s) = show s
    
  4. Использовать список полей.

Пример

Ваш пример может быть переписан как:

{-# LANGUAGE ExistentialQuantification #-}

main :: IO ()
main = do
 mapM_ print lst
 putStrLn "end"
 where
  lst :: [Printable]
  lst = [P (Out1 1),P (Out2 1 2)]

-- box type (2)
data Printable = forall a . Show a => P a

-- necessary Show instance for the box type (3)
instance Show Printable where show (P x) = show x

-- user defined:
data Out1 = Out1 Int deriving (Show)
data Out2 = Out2 Int Int deriving (Show)
8 голосов
/ 17 июня 2010

Вы уверены, что действительно хотите поместить в список различные типы?

Вы можете использовать что-то вроде jetxee с экзистенциальной квантификацией, но подумайте, что это на самом деле делает: у вас есть список терминов неизвестного типа, и единственное, что вы можете с ними сделать, это применить putOut, чтобы вернуть значение IO (). То есть , если «интерфейс» предоставляет только одну функцию с известным типом результата, нет разницы между списком существующих и списком результатов . Единственное возможное использование первого включает преобразование его во второе, так зачем добавлять дополнительный промежуточный шаг? Вместо этого используйте что-то вроде этого:

main :: IO ()
main = do
    sequence_ lst
    where lst :: [IO ()]
          lst = [out1 1, out2 1 2]

out1 x = putStrLn $ unwords ["Out1", show x]
out2 x y = putStrLn $ unwords ["Out2", show x, show y]

Поначалу это может показаться нелогичным, потому что оно опирается на некоторые необычные особенности Haskell. Рассмотрим:

  • Никаких дополнительных вычислений не выполняется - ленивая оценка означает, что show, unwords, & c. не будет запущен, если не выполнено действие IO.
  • Никакие побочные эффекты не связаны с простым созданием IO () значений - они могут храниться в списках, передаваться в чистом коде и т. Д. Их выполняет только функция sequence_ в main.

Тот же аргумент применяется к спискам "экземпляров Show" и еще много чего. Это не хорошо работает для экземпляров чего-то вроде Eq, где вам нужно два значения типа, но список экзистенциалов не будет работать лучше, потому что вы не знаете, есть ли два значения одного типа. Все, что вы могли бы сделать в в этом случае, - это проверить, чтобы каждый элемент был равен самому себе, а затем вы могли бы (как описано выше) просто создать список Bool s и покончить с этим.


В более общих случаях лучше иметь в виду, что классы типа Haskell не являются интерфейсами ООП . Классы типов являются мощным средством реализации специального полиморфизма, но они не так хорошо подходят для сокрытия деталей реализации. ООП-языки обычно объединяют специальный полиморфизм, повторное использование кода, инкапсуляцию данных, поведенческий подтип и т. Д., Связывая все в одной иерархии классов; в Хаскеле вы можете (и часто должны ) иметь дело с каждым по отдельности.

Объект на языке ООП - это, грубо говоря, набор (скрытых, инкапсулированных) данных, связанных с функциями для манипулирования этими данными, каждый из которых принимает инкапсулированные данные в качестве неявного аргумента (this, self , так далее.). Чтобы повторить это в Haskell, вам вообще не нужны классы типов:

  • Запишите каждый «метод класса» как обычную функцию с явно заданным параметром self.
  • Частично применить каждую функцию к значению «инкапсулированных» данных
  • Объединение частично примененных функций в один тип записи

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

Как и в более простом случае выше, это почти точно эквивалентно экзистенциальной версии; запись функций - это то, что вы получите, применив каждый метод класса type к каждому экзистенциальному.

Существуют некоторые классы типов, в которых использование записи функций не будет работать хорошо - например, Monad - которые, как правило, также являются теми же классами типов , которые не могут быть выражены обычными интерфейсами ООП. , как показывают современные версии C #, широко использующие монадический стиль, но не обеспечивающие какого-либо общего интерфейса IMonad.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...