Предполагаемая проверка типов обобщенной функции в качестве возвращаемого типа, но не типа аргумента - PullRequest
2 голосов
/ 26 мая 2019

Я изучаю типы SYB и ранга n и столкнулся с запутанным случаем того, что похоже на ограничение мономорфизма.

Я написал функцию для поиска самой мелкой записи, соответствующей предикату.Вместо сокращающей функции я хотел принять функцию, более похожую на предикат, используя Alternative, и сам преобразовать ее в обобщенную функцию.Я решил опустить аннотацию типа в блоке let, чтобы увидеть, как уменьшение мономорфизма повлияет на тип в этой реализации:

shallowest :: (Alternative f, Typeable b) => (b -> f a) -> GenericQ (f a)
shallowest p z =
  let op = (empty `mkQ` p) in
    op z <|> foldl (<|>) empty (gmapQ op z)

Это приводит к ошибке, которая предполагает, что неоднозначность в letПривязка не позволяет проверке проверки типов решить ограничение Data a1.

Error: • Couldn't match type ‘d’ with ‘a1’
  ‘d’ is a rigid type variable bound by
    a type expected by the context:
      forall d. Data d => d -> m a
  ‘a1’ is a rigid type variable bound by
    the type signature for:
      shallowest :: (b -> m a) -> GenericQ (m a)

(Другие тела, такие как head (gmapQ op z), вызывают явную ошибку неоднозначности для привязки let вдоль строк «Не удалось вывести (Типa0) в результате использования mkQ "; я также не выяснил, почему вышеприведенная форма этого не делает).

Ошибка типа исчезает, когда мы добавляем аннотацию в блок letдля op :: GenericQ (f a) (требуется ScopedTypeVariables).

Однако я запутался, что кажется, что ограничение Data на op может быть выведено: следующие проверки типов при возвращенииТип:

shallowest p = let { op = (empty `mkQ` p) } in op

Какая разница?В обоих случаях требуется op, чтобы быть forall d. Data d => d -> f a;Единственное отличие, которое я вижу, состоит в том, что первый находится в позиции аргумента, а второй - в позиции возврата.

Ответы [ 2 ]

2 голосов
/ 26 мая 2019

Во втором вашем фрагменте op на самом деле не полиморфен.

shallowest p = let { op = (empty `mkQ` p) } in op

Это тонкое различие: op на самом деле мономорфно, но в открытом контексте. С обычным обозначением для ввода суждений ввод op справа от in выглядит следующим образом:

 types         values
 ↓             ↓
 x, a, f, ...; op :: x -> f a, ... |- op :: x -> f a
                                            ↑
                                            monotype (no "forall")

 In English: "op has type (x -> f a) in the context consisting of type variables (x, a, f, ...) and values (op :: x -> f a, ...)"

shallowest становится полиморфным с помощью шага обобщения , который происходит на верхнем уровне. Если в контексте с переменными типа x, a, f, ... тело shallowest имеет тип x -> f a, то мы можем «закрыть контекст» и переместить переменные типа в тип shallowest :: forall x a f. x -> f a. Вывод типа выглядит следующим образом:

     x, a, f |- (let op = ... in op) :: x -> f a
 ⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻⸻ (generalization)
   |- (let op = .... in op) :: forall x a f. x -> f a

(Все усложняется классами типов и алгоритмами объединения, но это не относится к этому ответу.)

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

В Haskell обобщение происходит в следующих местах (список может быть не исчерпывающим), что является вполне естественным выбором:

  • определения функций, т. Е. let и привязки верхнего уровня по крайней мере с одним явным аргументом (здесь ограничение мономорфизма);

  • полиморфные аргументы функций более высокого ранга: если у вас есть функция f :: (forall a. w a) -> r, то f x будет обобщать a при проверке типов x;

  • и, конечно, при наличии явной аннотации _ :: forall a. t a.

0 голосов
/ 27 мая 2019

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

  • type GenericQ r = forall a . Data a => a -> r из syb и
  • gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u] от Data.Data.

Пожалуйста, дайте мне знать, если я ошибаюсь по этому поводу.Кроме того, любые forall s в дальнейшем будут записаны явно.


Здесь есть нечто большее, чем кажется на первый взгляд. Как предполагает Ли-Яо Ся , речь идет об обобщении типа op.Есть три важных факта о вашем первом определении shallowest:

  1. До обобщения , предполагаемый тип op равен Data d => d -> f a.Учитывая ограничение Data d, правило 1 ограничения мономорфизма (см. подраздел 4.5.5 отчета ) означает, что d в этом типе не может быть обобщено.

  2. В теле shallowest, op обнаруживается в двух местах.Первый - op z, с z :: a1, связанным и ограниченным на верхнем уровне подписью shallowest.В результате, это вхождение op не требует обобщения типа аргумента: для него тип op может быть forall f a. a1 -> f a, мономорфным в переменной типа a1 (я взял эту терминологию из подраздела 4.5.4 Отчета ).

  3. Тем не менее, другим случаем является gmapQ op z.gmapQ имеет тип ранга 2, требующий полиморфного аргумента.Таким образом, это вхождение требует обобщения типа аргумента op, как отмечалось в конце ответа Ли-Яо Ся.

# 1 и # 3 являются противоречивыми требованиями,и, таким образом, вы получаете ошибку типа, которую можно избежать, либо отключив ограничение мономорфизма, либо потребовав op быть полиморфным для типа аргумента с сигнатурой.Благодаря другому вхождению op, описанному в # 2, ситуация сообщается как несоответствие, связанное с двумя вхождениями.


Ниже приведен более минимальный расширенный пример, который может помочь увидеть, чтопродолжается.(Если вы собираетесь добавить следующие фрагменты в GHCi, кроме -XRankNTypes, вы также должны установить -XMonomorphismRestriction и -XNoExtendedDefaultRules, чтобы увидеть те же результаты.)

Это функция с рангом-2 типа, который будет играть роль gmapQ:

glub :: (forall x. Show x => x -> String) -> String
glub f = f 7

Теперь давайте попробуем сценарий, аналогичный сценарию shallowest ...

foo1 :: forall a. Show a => a -> String
foo1 x = bar x ++ glub bar
  where
  bar = show

...и есть ваша ошибка:

<interactive>:506:23: error:
    • Couldn't match type ‘x’ with ‘a’
      ‘x’ is a rigid type variable bound by
        a type expected by the context:
          forall x. Show x => x -> String
        at <interactive>:506:18-25
      ‘a’ is a rigid type variable bound by
        the type signature for:
          foo1 :: forall a. Show a => a -> String
        at <interactive>:505:1-38
      Expected type: x -> String
        Actual type: a -> String
    • In the first argument of ‘glub’, namely ‘bar’
      In the second argument of ‘(++)’, namely ‘glub bar’
      In the expression: bar x ++ glub bar
    • Relevant bindings include
        bar :: a -> String (bound at <interactive>:508:3)
        x :: a (bound at <interactive>:506:5)
        foo1 :: a -> String (bound at <interactive>:506:1)

Добавление символа подстановки, куда должна идти подпись bar, дает дополнительную ошибку, которая несколько более наводит на мысль:

foo2 :: forall a. Show a => a -> String
foo2 x = bar x ++ glub bar
  where
  bar :: _
  bar = show
• Found type wildcard ‘_’ standing for ‘a -> String’
  Where: ‘a’ is a rigid type variable bound by
           the type signature for:
             foo2 :: forall a. Show a => a -> String
           at <interactive>:511:1-38
  To use the inferred type, enable PartialTypeSignatures
• In the type signature: bar :: _
  In an equation for ‘foo2’:
      foo2 x
        = bar x ++ glub bar
        where
            bar :: _
            bar = show
• Relevant bindings include
    x :: a (bound at <interactive>:512:5)
    foo2 :: a -> String (bound at <interactive>:512:1)

Примечаниекак подстановочный знак "стоит для a -> String" указан как отдельный факт, а a не связан с сигнатурой типа foo2.Я считаю, что это соответствует различию между мономорфным в переменной типа и полиморфным, о котором я упоминал в пункте № 2 выше.

Если bar подпись полиморфного типа заставит его работать:

foo3 :: forall a. Show a => a -> String
foo3 x = bar x ++ glub bar
  where
  bar :: forall b. Show b => b -> String
  bar = show

То же самое делает определение стержня точечным, что обходит ограничение мономорфизма, делая его «привязкой функции», а не «простой привязкой шаблона» :

foo4 :: forall a. Show a => a -> String
foo4 x = bar x ++ glub bar
  where
  bar x = show x

Дляради полноты стоит отметить, что отсутствие ограничения на тип означает отсутствие ограничения мономорфизма:

foo5 :: forall a. Show a => a -> String
foo5 x = bar x ++ glub bar
  where
  bar = const "bar"

Смежная ситуация предполагает использование bar дважды, но без функции ранга 2:

foo6 x y = bar x ++ bar y
  where
  bar = show

Какой тип GHC выведет для foo6?

GHCi> :t foo6
foo6 :: Show a => a -> a -> [Char]

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

foo7 x y = bar x ++ bar y
  where
  bar :: forall a. Show a => a -> String
  bar = show
GHCi> :t foo7
foo7 :: (Show a1, Show a2) => a1 -> a2 -> [Char]

Поскольку я еще не упомянул об этом, вот аналог вашего второго shallowest:

foo8 :: forall a. Show a => a -> String 
foo8 x = bar x
  where
  bar = show

Это стоитEMPHпри условии, что bar здесь на самом деле не обобщается: оно мономорфно в переменной типа a.Мы все еще можем разбить этот пример, связавшись с foo7, а не с bar:

foo9 = bar
  where
  bar :: _
  bar = show

В этом случае bar не обобщается и не foo (теперь бессмысленно и без подписи). Это означает, что переменная мономорфного типа никогда не разрешается. В соответствии с правилом 2 ограничения мономорфизма оно становится переменной неоднозначного типа:

    <interactive>:718:14: error:
        • Found type wildcard ‘_’ standing for ‘a0 -> String’
          Where: ‘a0’ is an ambiguous type variable
          To use the inferred type, enable PartialTypeSignatures
        • In the type signature: bar :: _
          In an equation for ‘foo9’:
              foo9
                = bar
                where
                    bar :: _
                    bar = show
        • Relevant bindings include
            foo9 :: a0 -> String (bound at <interactive>:716:5)

<interactive>:719:13: error:
    • Ambiguous type variable ‘a0’ arising from a use of ‘show’
      prevents the constraint ‘(Show a0)’ from being solved.
      Relevant bindings include
        bar :: a0 -> String (bound at <interactive>:719:7)
        foo9 :: a0 -> String (bound at <interactive>:716:5)
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance Show a => Show (ZipList a)
          -- Defined in ‘Control.Applicative’
        instance Show Constr -- Defined in ‘Data.Data’
        instance Show ConstrRep -- Defined in ‘Data.Data’
        ...plus 64 others
        ...plus 250 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the expression: show
      In an equation for ‘bar’: bar = show
      In an equation for ‘foo9’:
          foo9
            = bar
            where
                bar :: _
                bar = show

Добавление сигнатуры типа к bar в определении foo9 не поможет - оно просто меняет точку, из которой сообщается об ошибке. Замена bar на что-то без ограничений устраняет ошибку, поскольку позволяет обобщать как bar, так и foo.

...