Как дополнительно ограничить существующий класс типов в Haskell

Есть ли способ дополнительно ограничить контекст существующего класса типов?

Например, класс типов Functor:

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

Это определение класса не требует, чтобы a или b были элементом Show. Также этот класс типов является классом, который я включил сам, поэтому я не могу влиять на определение классов. Можно ли позже разрешить только те a и те b, которые являются членами Show?


person frosch03    schedule 07.03.2012    source источник
comment
Было бы довольно интересно услышать, почему вы хотите иметь искусственные ограничения, которые не оправданы кодом. Возможно, есть недоразумения, которые можно разрешить.   -  person Ingo    schedule 07.03.2012
comment
Ограничения обычно накладываются на функции, а не на классы данных/типов. Причина в том, что вы никогда не будете знать, что вам нужно. Допустим, вы используете ShowFunctor из ответа Дэниела. Вы не можете сделать его аппликативным (и, следовательно, монадой), потому что аппликативы должны иметь возможность обертывать функции, а функция не имеет экземпляра show.   -  person mb14    schedule 07.06.2015


Ответы (2)


Не напрямую. Определение класса нельзя изменить без изменения исходного кода и перекомпиляции. В случае с классами, определенными в стандартных библиотеках, это приведет к значительному нарушению кода, поэтому это нереалистичный вариант.

Однако вы можете обернуть класс и добавить нужные ограничения,

class Functor f => ShowFunctor f where
    smap :: (Show a, Show b) => (a -> b) -> f a -> f b
    smap f = fmap f

а затем используйте этот класс вместо оригинала.

Но, возможно, вам не нужен дополнительный класс, и для ваших приложений достаточно определить smap на верхнем уровне и просто использовать его вместо fmap,

smap :: (Functor f, Show a, Show b) => (a -> b) -> f a -> f b
smap = fmap
person Daniel Fischer    schedule 07.03.2012
comment
Вероятно, ShowFunctor не должен быть подклассом Functor, поскольку он добавляет предварительное условие. Каждый Functor является ShowFunctor, но не обязательно наоборот. - person Philip JF; 07.03.2012
comment
Если цель состоит в том, чтобы еще больше ограничить существующий класс, я думаю, что сделать его подклассом — правильный путь. Если я полностью не понял, и OP хочет, чтобы экземпляры ShowFunctor могли обертывать только типы, принадлежащие Show (data ShowList a where ShowList :: Show a => [a] -> ShowList a; instance ShowFunctor ShowList where smap f (ShowList xs) = ShowList (fmap f xs)), но это не может быть принудительно применено классом (по крайней мере, не в H2010). - person Daniel Fischer; 07.03.2012
comment
@ frosch03 Можете ли вы уточнить, правильно ли я понял? - person Daniel Fischer; 07.03.2012
comment
@DanielFischer, вы правильно поняли, и я также считаю, что в этом случае имеет смысл сделать ShowFunctor подклассом Functor, хотя я также понимаю замечание @PhilipJF. - person frosch03; 07.03.2012
comment
Я думаю, это зависит от варианта использования. Я обычно думаю, что подклассы должны следовать принципам проектирования ООП (по крайней мере, мышление в стиле LSP), когда это возможно. В конце концов, классы Haskell — это просто объектно-ориентированные классы, за исключением того, что они не имеют состояния, принимают функции в своем конструкторе и автоматически передаются вместо того, чтобы рассматриваться как значения первого класса. - person Philip JF; 07.03.2012
comment
Виды ограничений! Они позволяют обрабатывать подобные ситуации в более широком смысле! blog.omega-prime.co.uk/?p=127 - person luqui; 07.03.2012
comment
@PhilipJF, с опозданием на три года, но нет, классы Haskell не очень похожи на классы OO. Такие классы, как Monoid, Eq и, возможно, наиболее очевидный Read, на самом деле вообще не подходят для объектно-ориентированной модели. - person dfeuer; 07.06.2015
comment
@dfeuer Да, они делают ... или, по крайней мере, после перевода словаря. - person Philip JF; 07.06.2015
comment
@PhilipJF, для меня это звучит немного натянуто. Подтипы на этом уровне являются чисто структурными, что не совсем так, как обычно работает ООП. - person dfeuer; 07.06.2015
comment
Просто обратите внимание, что ShowFunctor нельзя сделать аппликативным, если это необходимо. - person mb14; 07.06.2015
comment
@dfeuer подтип классов haskell является номинальным. Таким образом, вам нужно только номинальное подтипирование словарей и стандартное структурное правило для функций (которое вы получаете, если у вас есть co/contra variance и вы думаете о функциях как об общем... так что на любом разумном языке ООП) - это все, что вам нужно ввести преобразование передачи словаря. Подтипы, которые можно увидеть в чем-то вроде control.lens, технически номинальны, так как подклассы Haskell ниже. Это просто CPSed, перевернутый от того, к чему вы привыкли (открыть сверху, а не открыть снизу). - person Philip JF; 12.06.2015

Вы не можете сделать это, не сломав вещи (в настоящее время).

У вас есть несколько вариантов

  1. определите свой собственный ограниченный Functor класс
  2. не беспокойтесь об определении класса и просто определите функцию, которая делает то, что вы хотите
  3. используйте пакет RMonad
  4. изменять

На самом деле, теперь мы знаем, как разрешить экземплярам добавлять ограничения, так что, возможно, когда-нибудь это будет не так уж плохо, см. Подкатегории в Haskell для статьи, посвященной практически именно этому вопросу. Синтаксис в этой статье немного отличается от того, что в настоящее время работает в GHC, но в основном мы хотели бы переопределить класс Functor, чтобы он выглядел так:

class Functor f where
   type SubCat f :: * -> Constraint -- associated constraint
   type SubCat f = () -- default definition
   fmap :: (SubCat f a, SubCat f b) => (a -> b) -> f a -> f b
person Philip JF    schedule 07.03.2012