Ответ 1
Эта проблема представляет собой поворотную точку между объектно-ориентированным и функциональным мышлением. Иногда даже сложные Haskellers все еще находятся в этом ментальном переходе, и их проекты часто попадают в шаблон
data Plane = Plane { point :: Point, normal :: Vector Double }
data Sphere = Sphere { center :: Point, radius :: Double }
class Shape s where
intersect :: s -> Ray -> Maybe Point
surfaceNormal :: s -> Point -> Vector Double
Я также сделал экземпляры Plane
и Sphere
Shape
.
Я пытаюсь сохранить сферы и плоскости в том же списке, но это не сработает. Я понимаю, что это не должно работать, потому что Sphere
и Plane
- два разных типа, но оба экземпляра Shape
, так что не должно ли это работать? Как сохранить фигуры и плоскости в списке?
shapes :: (Shape t) => [t]
shapes = [ Sphere { center = Point [0, 0, 0], radius = 2.0 },
Plane { point = Point [1, 2, 1], normal = 3 |> [0.5, 0.6, 0.2] }
]
Эта проблема представляет собой поворотную точку между объектно-ориентированным и функциональным мышлением. Иногда даже сложные Haskellers все еще находятся в этом ментальном переходе, и их проекты часто попадают в шаблон
Вы ищете гетерогенный список, который большинству Haskellers не особенно нравится, даже если они задавали себе этот же вопрос при первом изучении Haskell.
Вы пишете:
shapes :: (Shape t) => [t]
Это говорит о том, что в списке есть тип t
, все из которых одинаковы и представляют собой форму (ту же форму!). Другими словами - нет, он не должен работать, как у вас есть.
Два распространенных способа обработки (сначала путь Haskell 98, а затем отличный способ, который я не рекомендую второй):
Использовать новый тип для статического объединения интересующих подтипов:
data Foo = F deriving Show
data Bar = B deriving Show
data Contain = CFoo Foo | CBar Bar deriving Show
stuffExplicit :: [Contain]
stuffExplicit = [CFoo F, CBar B]
main = print stuffExplicit
Это приятно видеть, как прямо, и вы не теряете никакой информации о том, что содержится в списке. Вы можете определить, что первым элементом является Foo
, а второй элемент - Bar
. Недостатком, как вы, наверное, уже поняли, является то, что вы должны явно добавить каждый тип компонента, создав новый конструктор типа Contain
. Если это нежелательно, продолжайте чтение.
Использовать экзистенциальные типы. Еще одно решение включает в себя потерю информации об элементах - вы просто сохраняете, скажем, знание о том, что элементы находятся в определенном классе. В результате вы можете использовать только операции из этого класса в элементах списка. Например, ниже будет только помнить, что элементы относятся к классу Show
, поэтому единственное, что вы можете сделать с элементами, это использовать функции, которые являются полиморфными в Show
:
data AnyShow = forall s. Show s => AS s
showIt (AS s) = show s
stuffAnyShow :: [AnyShow]
stuffAnyShow = [AS F, AS B]
main = print (map showIt stuffAnyShow)
Для этого требуется некоторое расширение языка Haskell, а именно ExplicitForAll
и ExistentialQuantification
. Нам нужно было явно определить showIt
(используя сопоставление с образцом, чтобы деконструировать тип AnyShow
), потому что вы не можете использовать имена полей для типов данных, которые используют экзистенциальную квантификацию.
Существует больше решений (надеюсь, что в другом ответе будет использоваться Data.Dynamic
- если никто не сделает этого, и вы заинтересованы, тогда прочитайте его и не стесняйтесь публиковать какие-либо вопросы, которые генерирует чтение).