Подтвердить что ты не робот

Список различных типов?

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] }
         ]
4b9b3361

Ответ 1

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

Ответ 2

Вы ищете гетерогенный список, который большинству 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 - если никто не сделает этого, и вы заинтересованы, тогда прочитайте его и не стесняйтесь публиковать какие-либо вопросы, которые генерирует чтение).