Неоднозначная переменная типа «бла» в ограничении ... как исправить? - PullRequest
6 голосов
/ 15 февраля 2011

Я пытаюсь написать простой трассировщик лучей на Хаскеле. Я хотел определить класс типов, представляющий различные виды доступных поверхностей, с функцией определения, где луч пересекает их:

{-# LANGUAGE RankNTypes #-}

data Vector = Vector Double Double Double
data Ray = Ray Vector Vector

class Surface s where
  intersections :: s -> Ray -> [Vector]

-- Obviously there would be some concrete surface implementations here...

data Renderable = Renderable
  { surface    :: (Surface s) => s
  , otherStuff :: Int
  }

getRenderableIntersections :: Renderable -> Ray -> [Vector]
getRenderableIntersections re ra = intersections (surface re) ra

Однако это дает мне ошибку:

Ambiguous type variable 's' in the constraint:
  'Surface'
    arising from a use of 'surface'

(Фактический код более сложный, но я пытался перевести его на что-то более простое, сохраняя при этом суть того, чего я пытаюсь достичь).

Как мне это исправить? Или, в качестве альтернативы, учитывая, что я пришел из стандартного ОО, что я в принципе делаю неправильно?

Ответы [ 2 ]

10 голосов
/ 15 февраля 2011

Пожалуйста, не используйте экзистенциальные типы для этого! Вы могли бы, но не было бы никакого смысла.

С функциональной точки зрения вы можете полностью отбросить это понятие класса типов поверхности. Surface - это то, что отображает Луч в список Векторов, не так ли? Итак:

type Surface = Ray -> [Vector]

data Renderable = Renderable
 { surface    :: Surface
 , otherStuff :: Int
}

Теперь, если вы действительно хотите, вы можете получить класс типов ToSurface, который вы указали:

class ToSurface a where
     toSurface :: a -> Surface

Но это только для удобства и специального полиморфизма. Ничто в вашей модели не требует этого.

В целом, существует очень мало вариантов использования для экзистенциалов, но, по крайней мере, в 90% случаев вы можете заменить экзистенциал функциями, которые он представляет, и получить что-то более чистое и более простое для рассуждения.

Кроме того, даже несмотря на то, что это может быть слишком сложным для вас, и проблемы не совсем совпадают, вам могут пригодиться некоторые из работ Конала по денотационному дизайну: http://conal.net/blog/posts/thoughts-on-semantics-for-3d-graphics/

3 голосов
/ 15 февраля 2011

В вашей функции getRenderableIntersections вы вызываете surface. Интерпретатор не может выяснить, какой экземпляр класса Surface вы хотите использовать. Если у вас есть два таких экземпляра:

instance Surface SurfaceA where
  -- ...
instance Surface SurfaceB where
  -- ...

Как переводчик может определить тип surface?

То, как вы определили Renderable, означает, что есть функция surface :: Surface s => Renderable -> s.

Попробуйте создать экземпляр Surface SurfaceA и задать следующий запрос типа (с учетом простого конструктора SurfaceA):

> :t surface (Renderable SurfaceA 0) -- What's the type of the expression?

Итак, какого типа это выражение? Могу поспорить, что вы ожидаете SurfaceA. Неправильно. Возьмите тип surface. Он принимает аргумент Renderable, и мы передаем ему аргумент Renderable. Что осталось после этого? Surface s => s. Это тип этого выражения. Мы до сих пор не знаем, какой тип представляет s.

Если вы хотите, чтобы тип был SurfaceA, вам нужно изменить свой код, чтобы он стал чем-то вроде surface :: Surface s => Renderable s -> s. Таким образом, можно определить, что такое s, потому что это то же самое s, которое используется в Renderable.

РЕДАКТИРОВАТЬ: Как предложено @mokus, вы также можете попробовать расширение ExistentialTypes. Это позволяет "скрывать" параметры типа справа от объявления типа.

data Renderable = forall s. Surface s => Renderable
  { surface    :: s
  , otherStuff :: Int
  }

На странице HaskellWiki, на которую я ссылался выше, даже есть пример , очень похожий на то, что вы хотите сделать.

РЕДАКТИРОВАТЬ: (@stusmith) - Для справки, я включаю код ниже, который компилируется на основе этих предложений здесь. Однако я принял ответ, который, я думаю, показывает лучший способ приблизиться к вещам.

{-# LANGUAGE ExistentialQuantification #-}

data Vector = Vector Double Double Double
data Ray = Ray Vector Vector

class Surface_ s where
  intersections :: s -> Ray -> [Vector]

data Surface = forall s. Surface_ s => Surface s

instance Surface_ Surface where
  intersections (Surface s) ra = intersections s ra

data Renderable = Renderable
  { surface :: Surface
  }

getRenderableIntersections :: Renderable -> Ray -> [Vector]
getRenderableIntersections re ra = intersections (surface re) ra
...