Конфликт функциональных зависимостей Haskell

Почему это приводит к конфликту?

class Foo a b | b -> a where
  foo :: a -> b -> Bool

instance Eq a => Foo a a where
  foo = (==)

instance Eq a => Foo a (a -> a) where
  foo x f = f x == x

Обратите внимание, что код будет скомпилирован, если я удалю функциональную зависимость.

У меня сложилось впечатление, что функциональные зависимости должны запрещать только следующие вещи, хотя на самом деле они компилируются!

class Foo a b | b -> a where
  foo :: a -> b -> Bool

instance Eq a => Foo a a where
  foo = (==)

instance Eq a => Foo Bool a where
  foo _ x = x == x

Тот же параметр b, но другие параметры a. Не следует ли b -> a запретить это, поскольку это означает, что a однозначно определяется b?


person Thomas Eding    schedule 15.09.2011    source источник


Ответы (2)


Пробовали ли вы на самом деле использовать вторую версию? Я предполагаю, что во время компиляции экземпляров вы начнете получать двусмысленность и ошибки перекрытия при вызове foo.

Самым большим камнем преткновения здесь является то, что fundeps не взаимодействуют с переменными типа так, как вы могли бы ожидать, — выбор экземпляра на самом деле не ищет решения, он просто слепо сопоставляется, пытаясь унифицировать. В частности, когда вы пишете Foo a a, a является совершенно произвольным и, таким образом, может унифицироваться с таким типом, как b -> b. Когда второй параметр имеет форму b -> b, он соответствует обоим экземплярам, ​​но фундэпсы говорят, что в одном случае первый параметр должен быть b -> b, а в другом — b. Отсюда и конфликт.


Поскольку это явно удивляет людей, вот что произойдет, если вы попытаетесь использовать вторую версию:

  • bar = foo () () приводит к:

    Couldn't match type `Bool' with `()'
      When using functional dependencies to combine
        Foo Bool a,
    

    ... потому что fundep говорит через второй экземпляр, что любой тип в качестве второго параметра однозначно определяет Bool как первый. Таким образом, первый параметр должен быть Bool.

  • bar = foo True () приводит к:

    Couldn't match type `()' with `Bool'
      When using functional dependencies to combine
        Foo a a,
    

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

  • bar = foo () True приводит к ошибкам из-за обоих экземпляров, так как на этот раз они соглашаются, что первый параметр должен быть Bool.

  • bar = foo True True приводит к:

    Overlapping instances for Foo Bool Bool
      arising from a use of `foo'
    

    ... потому что оба экземпляра удовлетворены и, следовательно, перекрываются.

Довольно весело, да?

person C. A. McCann    schedule 15.09.2011
comment
как ни странно, вторая версия действительно работает, хотя я не могу понять, почему! - person sclv; 15.09.2011
comment
Но если экземпляры компилируются, какой смысл в функциональных зависимостях? Я думал, что они были именно для предотвращения компиляции такого рода вещей! - person Daniel Wagner; 15.09.2011
comment
@Daniel Wagner: Написание экземпляров, которые никогда не могут работать, но принимаются компилятором, если вы не пытаетесь их использовать, является известным недостатком fundeps, насколько мне известно. Это одна из причин, по которой я предпочитаю семейства шрифтов для большинства целей. - person C. A. McCann; 15.09.2011

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

Правка. Сначала я предположил, что работает второй пример. Это было сделано на ghc 7.0.4, но это не имело смысла, и эта проблема, похоже, была решена в более новых версиях.

person sclv    schedule 15.09.2011
comment
Второй пример не работает. Просто GHC нерешителен в отношении экземпляров из-за полиморфизма. Если вы дадите ему что-то более явное или конкретный тип, для которого нужно найти экземпляр, он будет кричать на вас. - person C. A. McCann; 15.09.2011
comment
Чтобы быть точным, второй пример говорит о том, что для любого b первый параметр однозначно определяется как b и Bool. Единственное использование foo, которое удовлетворяет обоим ограничениям, — это когда требуется экземпляр Foo Bool Bool, и в этом случае вместо этого он жалуется на перекрытие. - person C. A. McCann; 15.09.2011
comment
@К.А. McCann см. мою правку выше. Я знаю, что пример говорит, но я проверил его, и он делает другое. - person sclv; 15.09.2011
comment
Смотрите также редактирование моего ответа. :] Это копипаста из GHCi. Использование String в качестве второго аргумента дает мне тот же результат, что и (). Какую версию GHC вы используете? - person C. A. McCann; 15.09.2011
comment
@К.А. Макканн -- 7.0.4. Возможно, это ошибка, которая была исправлена ​​в более поздних версиях? - person sclv; 15.09.2011
comment
Я использовал 7.2.1, но не могу найти ничего подходящего в примечаниях к выпуску. Возможно, это было побочным исправлением изменений, допускающих ограничения равенства в контекстах классов? - person C. A. McCann; 16.09.2011
comment
@К.А. Макканн -- хм. быстрое сканирование трассировки не привело к какой-либо ошибке, которая выскочила бы на меня как таковую, но выявило, что решатель fundep подвергся капитальному ремонту или двум с версии 7.0.4, так что это может быть что угодно из ряда вещей . В любом случае, мы можем оставить это как загадку, в основном раскрытую. - person sclv; 16.09.2011
comment
Интересно, что на 6.10.4 я получаю тот же результат, что и на 7.2.1. - person C. A. McCann; 16.09.2011