`принуждение` и создание экземпляров переменных типа

Рассмотрим следующий сеанс GHCI:

>:set -XTypeApplications
>import Data.Map.Strict
>import GHC.Exts
>newtype MySet a = MySet (Map a ())
>let member' :: Ord a => a -> MySet a -> Bool; member' = coerce member

<interactive>:21:57: error:
    * Couldn't match representation of type `a0' with that of `()'
        arising from a use of `coerce'
    * In the expression: coerce member
      In an equation for member': member' = coerce member
>let member' :: Ord a => a -> MySet a -> Bool; member' = coerce (member @_ @())

У меня есть подозрение, что здесь происходит: средство проверки типов должно удовлетворять Coercible (Ord a => a -> Map a b -> Bool) (Ord a => a -> MySet a -> Bool) и не может преобразовать b в этом ограничении в ().

Есть ли более элегантный способ, чем сделать это с помощью -XTypeApplications?

Изменить: я особенно ищу решения, которые касаются многих вхождений MySet a в типе, например union :: Ord a => MySet a -> MySet a -> MySet a.


person Sebastian Graf    schedule 04.09.2017    source источник
comment
Как насчет member' k s = member k (coerce s)?   -  person Benjamin Hodgson♦    schedule 04.09.2017
comment
Хорошая мысль, но это становится довольно многословным для union (где MySet встречается в типе несколько раз) и вообще невозможно для fromList.   -  person Sebastian Graf    schedule 04.09.2017
comment
Я думаю, что это может быть серьезной ошибкой в ​​​​GHC, и, безусловно, стоит билета. Или поговорите с Ричардом в Оксфорде :-)   -  person Joachim Breitner    schedule 04.09.2017
comment
@JoachimBreitner, я подозреваю, что это будет довольно сложно исправить. Смотрите мой ответ. Конечно, вы с Ричардом оба знаете гораздо больше, чем я.   -  person dfeuer    schedule 05.09.2017


Ответы (2)


member :: Ord a => a -> Map a b -> Bool
member' :: Ord a => a -> MySet a -> Bool

GHC должен принять

Coercible (Map a b) (MySet a)

Он видит, что

Coercible (MySet a) (Map a ())

что оставляет его нуждающимся

Coercible (Map a ()) (Map a b)

что приводит к

Coercible () b

Но что такое b? Это неоднозначно. В этом случае не имеет значения, что такое b, потому что по параметричности member может быть все равно. Так что было бы совершенно разумно выбрать b ~ () и тривиально разрешить принуждение. Но GHC обычно не выполняет такой параметрический анализ при выводе типов. Я подозреваю, что это может быть сложно изменить. В частности, каждый раз, когда вывод типа «угадывается», существует риск, что он может ошибиться и заблокировать вывод где-то еще. Это большая банка червей.

Что касается вашей проблемы, у меня нет хорошего решения. Когда у вас есть несколько функций с похожими шаблонами, вы можете абстрагироваться от них, но вы все равно столкнетесь со значительным раздражением.

person dfeuer    schedule 05.09.2017
comment
Это похоже на то, что мне подсказала моя интуиция, но я не был уверен. Спасибо! Использование -XTypeApplications довольно утомительно, но работает: github. com/sgraf812/pomaps/blob/ - person Sebastian Graf; 06.09.2017
comment
Кстати, я думаю, что третья и четвертая Coercible поменялись местами. Не то чтобы это что-то меняет в самой идее. - person Sebastian Graf; 06.09.2017
comment
@SebastianGraf, если вы упаковываете целый модуль, вам может быть проще использовать подписи полного типа, чем приложения типов. foo = coerce (M.foo :: theoldthing this that) :: forall this that. thenewthing this that). Таким образом, можно больше копировать/вставлять, меньше думая о том, какие аргументы типа куда помещаются. - person dfeuer; 06.09.2017

Решение с TypeApplications довольно простое:

{-# LANGUAGE TypeApplications #-}

import Data.Coerce
import qualified Data.Map as M

newtype Set a = Set (M.Map a ())

member :: Ord a => a -> Set a -> Bool
member = coerce (M.member @_ @())

union :: Ord a => Set a -> Set a -> Set a
union = coerce (M.union @_ @())

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

smap :: (Ord b) => (a -> b) -> Set a -> Set b
smap = coerce (M.mapKeys @_ @_ @())

Чтобы определить, как именно вы должны указать тип приложения (помимо проб и ошибок), используйте

>:set -fprint-explicit-foralls
>:i M.mapKeys
M.mapKeys ::
  forall k2 k1 a. Ord k2 => (k1 -> k2) -> M.Map k1 a -> M.Map k2 a

Порядок переменных, который вы получаете от :i, такой же, как и у TypeApplications.

Обратите внимание, что вы не можете использовать coerce вместо fromList — это не ограничение, это просто не имеет смысла:

fromList :: Ord a => [a] -> Set a
fromList = coerce (M.fromList @_ @())

Это дает ошибку

* Couldn't match representation of type `a' with that of `(a, ())'

Лучшее, что вы можете сделать здесь, вероятно,

fromList :: Ord a => [a] -> Set a
fromList = coerce (M.fromList @_ @()) . map (flip (,) ())
person user2407038    schedule 05.09.2017
comment
NB: в ОП ясно сказано, что они знают о решении TypeApplications, но я думаю, что оно «достаточно простое», поэтому этот ответ по существу нет, более элегантного способа нет. Но это кажется в значительной степени основанным на мнении, поэтому вместо этого я формулирую сам ответ как (простой!) Метод, который можно использовать для механического написания такого кода, не задумываясь о том, как это сделать. - person user2407038; 06.09.2017
comment
:set -fprint-explicit-foralls похоже на то, к чему я стремился, если бы знал об этом при реализации github.com/sgraf812/pomaps/blob/. Спасибо за ваши советы, но я думаю, что другой пост является более прямым ответом на вопрос. - person Sebastian Graf; 06.09.2017