Как можно утверждать ограничение класса в экземпляре класса, для которого требуется конструктор типа, а не конкретный тип? - программирование
Подтвердить что ты не робот

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

В настоящее время я в Глава 8 Учите вас Haskell, и я дошел до раздела на Functor typeclass. В этом разделе автор приводит примеры того, как могут быть созданы различные типы экземпляров класса (например, Maybe, пользовательский тип Tree и т.д.). Увидев это, я решил (для удовольствия и практики) попробовать реализовать экземпляр для Data.Set; во всех этих случаях, конечно, игнорируя Data.Set.map.

Фактический экземпляр сам по себе довольно прямолинейный, и я написал его как:

instance Functor Set.Set where
  fmap f empty = Set.empty
  fmap f s = Set.fromList $ map f (Set.elems s)  

Но, поскольку я использую функцию fromList, это приводит к ограничению класса, вызывающему типы, используемые в Set быть Ord, что объясняется ошибкой компилятора:

Error occurred
ERROR line 4 - Cannot justify constraints in instance member binding
*** Expression    : fmap
*** Type          : Functor Set => (a -> b) -> Set a -> Set b
*** Given context : Functor Set
*** Constraints   : Ord b

Смотрите: Пример Live

Я попытался установить ограничение на экземпляр или добавить подпись типа к fmap, но ни одно из них не удалось (оба были ошибками компилятора.)

Учитывая такую ​​ситуацию, как можно выполнить и удовлетворить ограничение? Есть ли способ?

Спасибо заранее!:)

4b9b3361

Ответ 1

К сожалению, нет простого способа сделать это со стандартным классом Functor. Вот почему Set не поставляется с экземпляром Functor по умолчанию: вы не можете его написать.

Это что-то вроде проблемы, и были некоторые предлагаемые решения (например, определение класса Functor по-другому), но я не знаю, существует ли консенсус относительно того, как наилучшим образом справиться с этим.

Я считаю, что один из подходов состоит в том, чтобы переписать класс Functor, используя типы ограничений, чтобы подтвердить дополнительные экземпляры ограничений нового Functor класс может иметь. Это позволит вам указать, что Set должен содержать типы из класса Ord.

В другом подходе используются только многопараметрические классы. Я могу найти статью о том, как это сделать для класса Monad, но сделать Set часть Monad сталкивается с теми же проблемами, что и сделать ее частью Functor. Он называется Ограниченные монады.

Основной смысл использования многопараметрических классов здесь выглядит примерно так:

class Functor' f a b where
  fmap' :: (a -> b) -> f a -> f b

instance (Ord a, Ord b) => Functor' Data.Set.Set a b where
  fmap' = Data.Set.map

По существу, все, что вы делаете здесь, делает типы в Set также частью класса. Это позволяет вам ограничить то, что эти типы могут быть, когда вы пишете экземпляр этого класса.

Эта версия Functor требует двух расширений: MultiParamTypeClasses и FlexibleInstances. (Чтобы иметь возможность определить класс и второе расширение, вам нужно, чтобы первое расширение имело возможность определить экземпляр для Set.)

Haskell: Пример складки, который не является Functor (или не Traversable)?, хорошо обсуждает это.

Ответ 2

Это невозможно. Цель класса Functor заключается в том, что если у вас есть Functor f => f a, вы можете заменить a на что угодно. Класс не позволяет ограничивать вас только тем, что он возвращает. Поскольку Set требует, чтобы его элементы удовлетворяли определенным ограничениям (и, действительно, это не деталь реализации, а действительно существенное свойство множеств), он не удовлетворяет требованиям Functor.

Есть, как упоминалось в другом ответе, способы разработки класса, такого как Functor, который ограничивает вас таким образом, но это действительно другой класс, поскольку он дает пользователю класса меньше гарантий (вы не знаете, t использовать это с любым параметром типа, который вы хотите), в обмен на то, чтобы стать применимым к более широкому диапазону типов. То есть, в конце концов, классический компромисс определения свойства типов: чем больше типов вы хотите его удовлетворить, тем меньше они должны быть вынуждены удовлетворять.

(Еще один интересный пример того, где это показано, - это класс MonadPlus. В частности, для каждого экземпляра MonadPlus TC вы можете создать экземпляр Monoid (TC a), но вы не всегда можете пойти наоборот. экземпляр Monoid (Maybe a) отличается от экземпляра MonadPlus Maybe, поскольку первый может ограничить a, но последний не может.)

Ответ 3

Вы можете сделать это, используя CoYoneda Functor.

{-# LANGUAGE GADTs #-}

data CYSet a where
    CYSet :: (Ord a) => Set.Set a -> (a -> b) -> CYSet b

liftCYSet :: (Ord a) => Set.Set a -> CYSet a
liftCYSet s = CYSet s id

lowerCYSet :: (Ord a) => CYSet a -> Set.Set a
lowerCYSet (CYSet s f) = Set.fromList $ map f $ Set.elems s

instance Functor CYSet where
  fmap f (CYSet s g) = CYSet s (f . g)

main = putStrLn . show
  $ lowerCYSet
  $ fmap (\x -> x `mod` 3)
  $ fmap abs
  $ fmap (\x -> x - 5)
  $ liftCYSet $ Set.fromList [1..10]
-- prints "fromList [0,1,2]"