2013-05-28 3 views
2

Вот мой минимальный пример:Как я могу устранить двусмысленность этого типа?

{-# LANGUAGE MultiParamTypeClasses, RankNTypes #-} 

import Control.Lens 

class Into outer inner where 
    factory :: inner -> outer 
    merge :: inner -> inner -> inner 

-- Given an inner item, a lens and an outer item, use factory to construct a new 
-- outer around the inner if the Maybe outer is Nothing, or else use merge to combine 
-- the argument inner with the one viewed through the lens inside the outer 
into :: Into outer inner => 
    inner -> Lens' outer inner -> Maybe outer -> Maybe outer 
inner `into` lens = Just . maybe (factory inner) (over lens (merge inner)) 

Это не может скомпилировать со следующей ошибкой:

GHCi, version 7.6.2: http://www.haskell.org/ghc/ :? for help 
Loading package ghc-prim ... linking ... done. 
Loading package integer-gmp ... linking ... done. 
Loading package base ... linking ... done. 
[1 of 1] Compiling Main    (foo.hs, interpreted) 

foo.hs:10:62: 
    Could not deduce (Into outer0 inner) arising from a use of `merge' 
    from the context (Into outer inner) 
     bound by the type signature for 
       into :: Into outer inner => 
         inner -> Lens' outer inner -> Maybe outer -> Maybe outer 
     at foo.hs:9:9-84 
    The type variable `outer0' is ambiguous 
    Possible fix: add a type signature that fixes these type variable(s) 
    In the second argument of `over', namely `(merge inner)' 
    In the second argument of `maybe', namely 
     `(over lens (merge inner))' 
    In the second argument of `(.)', namely 
     `maybe (factory inner) (over lens (merge inner))' 
Failed, modules loaded: none. 
Prelude> 

Я понимаю, почему происходит эта ошибка; что для вызова merge может использоваться другой экземпляр Into (с другим outer, но тот же inner), что и тот, который был выбран ограничением по всей функции into. Но я не могу понять, как это решить.

Что я пробовал:

  • Использование функциональных зависимостей, подразумевает outer от inner; это приблизилось к работе (жаловался на необходимость UndecidableInstances), но не кажется совершенно правильным; в идеале я бы действительно хотел иметь способ нажимать то же самое inner на два разных outer s
  • Использование ассоциированного синонима типа для того же; кроме углекинга подписи типа (outer =>Outer inner), я также упал, потому что outer, который я использую в экземпляре, имеет больше переменных типа (один из них фантом), чем inner, то есть я не смог законно создать экземпляр связанный тип в заявлении экземпляра
  • Добавление явной подписи типа при использовании merge в into с ScopedTypeVariables для привязки его к типу подписи для into; но так как тип merge не относится к outer это не помогает

Есть ли способ, что я могу быть явно об использовании того же класса экземпляра типа для merge как для всей into? Или каким-либо другим способом я могу ограничить систему типов, требующую этого? В идеале я хотел бы держать класс так, чтобы мои объявления экземпляров все еще это просто:

instance (Hashable v, Eq v) => Into (VarInfo s k v) (HashSet v) where 
    -- VarInfo is just a record type with 2 fields, the second being a HashSet v 
    factory = VarInfo (return()) 
    merge = HashSet.intersection 
+0

Примечание: 'Lens'' является полиморфным, поэтому функция, которая принимает его как аргумент, получает тип ранга-2. Если вы просто используете 'over', вы можете дать ему более простой тип ранга 1 (который также будет более полиморфным, поскольку он будет работать для' Setter' и т. Д.). (Даже если вы используете каждую операцию с объективом, вы можете принять 'ALens'', а затем использовать' cloneLens' или что-то в этом роде.) – shachaf

+0

@shachaf Спасибо; Я все еще обволакиваю все содержимое пакета объективов.Я знал, что «Ленс» был менее общим, чем нужно, но у меня есть линзы, и я знаю, что означает «Ленс», тогда как предполагаемый тип для этого параметра был непонятным тарабарщиной для меня на этом этапе (не уверен, что это был бы дружественный синоним, если бы он был). В чем преимущество типов ранга 1? – Ben

+0

Главным преимуществом является то, что вывод типа для классов ранга 2 не очень хорошо работает в GHC. В конечном итоге вы нуждаетесь в eta-expand, напишите явные типы, где GHC, как правило, сможет их вывести и т. Д. Также этот тип распространяется на каждого пользователя вашей функции - вам требуется больше полиморфизма, чем это необходимо. (Возможно, также могут быть последствия для производительности, так как вам нужно передавать явные словари, если все не встанет в очередь ... Я еще не проверил.) – shachaf

ответ

5

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

Решение состоит в том, чтобы сделать иерархию классов более точной. Здесь вы можете создать второй класс для merge:

class Mergeable a where 
    merge :: a -> a -> a 

class Mergeable inner => Into outer inner where 
    factory :: inner -> outer 

Вы можете быть в состоянии использовать более общий Semigroup класс, а не класс одноранговой Mergeable тоже, но это зависит от деталей вашего приложения и свойства merge.

+0

Привет, это кажется почти очевидным сейчас. Я уже рассматривал использование Monoid для операции слияния, но один из моих случаев - это пересечение, а нуль для моноида множеств под пересечением должен был быть полным набором для типа элемента. Создание экземпляра Semigroup, противоречащего Monoid, не будет хорошей идеей. В другом из моих случаев я не думаю, что операция ассоциативна. Этот класс является просто внутренним DRY, который не отображается в моем интерфейсе, поэтому я не слишком обеспокоен, если он немного специализирован. – Ben

Смежные вопросы