Возникли проблемы с написанием моего fmap

Я пытаюсь написать fmap для этого типа

data Triangle a  = Triangle {t0 :: Point a, t1 ::  Point a, t2 ::  Point a}

где Point определяется как

data Point a = Point {px :: a, py :: a, pz :: a}

И мой экземпляр def

instance Functor Triangle where 
    fmap f (Triangle v0 v1 v2) = Triangle (f v0) (f v1) (f v2)

Я получаю следующее сообщение об ошибке и не могу понять, почему

 C:\Scripts\Haskell\Geometry.hs:88:1:
     Occurs check: cannot construct the infinite type: a = Point a
     When generalising the type(s) for `fmap'
     In the instance declaration for `Functor Triangle'

Любые идеи?


person Jonathan Fischoff    schedule 15.12.2009    source источник


Ответы (3)


instance Functor Point where
    fmap f (Point v0 v1 v2) = Point (f v0) (f v1) (f v2)

instance Functor Triangle where
    fmap f (Triangle v0 v1 v2) = Triangle (fmap f v0) (fmap f v1) (fmap f v2)

В экземпляре Triangle f равно a -> b. Сначала мы должны преобразовать его в Point a -> Point b. Затем мы можем сделать fmap f преобразовать Triangle a в Triangle b. (Обратите внимание, что вы применяете f к 9 объектам, если я правильно понял ваше намерение) [редактировать: было 27]

person sdcvvc    schedule 15.12.2009
comment
Понятно. Я не понимал, что fmap нужно применять к таким компонентам. Я понимаю, что то же самое и с Traversable. Спасибо! - person Jonathan Fischoff; 16.12.2009

Предыдущий ответ дает вам правильное решение, но может быть полезно более подробно рассказать о том, что здесь происходит. Тип fmap -

fmap :: Functor f => (a -> b) -> f a -> f b

Таким образом, определение типа для вашего объявления instance происходит следующим образом:

  1. In fmap f (Triangle v0 v1 v2), f must have some type a -> b and (Triangle v0 v1 v2) must have type Triangle a.
    • By the definition of Triangle, v0, v1, and v2 must have type Point a.
    • Поскольку f применяется к v0, v1 и v2, его тип аргумента a должен быть Point a.
    • Ой, a = Point a неудовлетворительно.

Почему определение Triangle (fmap f v0) (fmap f v1) (fmap f v2) работает? :

  1. In fmap f (Triangle v0 v1 v2), f must have some type a -> b and (Triangle v0 v1 v2) must have type Triangle a.
    • By the definition of Triangle, v0, v1, and v2 must have type Point a.
    • Предполагая, что Point является экземпляром Functor, как указано выше, fmap f v0 должен иметь тип Point b, где b - это тип результата f. То же самое для v1 и v2.
    • Следовательно, Triangle (fmap f v0) (fmap f v1) (fmap f v2) имеет тип Triangle b.
    • QED.
person Chris Conway    schedule 16.12.2009
comment
Я нахожу этот вид пошагового руководства очень ценным при изучении того, как работает вывод типов, спасибо, Крис! - person janne; 16.12.2009

Кстати, интересным свойством Functor является то, что существует только один возможный экземпляр для создания, который удовлетворяет Functor законы.

Более того, этот экземпляр может быть автоматически создан для вас с помощью derive пакет:

{-# LANGUAGE TemplateHaskell #-}

import Data.DeriveTH (derive, makeFunctor)

data Point a = Point {px :: a, py :: a, pz :: a}
$(derive makeFunctor ''Point)

data Triangle a  = Triangle {t0 :: Point a, t1 ::  Point a, t2 ::  Point a}
$(derive makeFunctor ''Triangle)

Имхо, это частично выигрыш, потому что, если вы решите изменить определение Triangle, его экземпляр Functor будет сохранен автоматически для вас.

person yairchu    schedule 17.12.2009
comment
Производная версия в Cabal не работала в то время, когда я писал этот вопрос. Однако сейчас я использую для этого версию darc. - person Jonathan Fischoff; 19.12.2009
comment
@Jonathan Fischoff: да, наверное, я тоже использовал версию darcs - person yairchu; 20.12.2009