Когда -XAllowAmbiguousTypes подходит?


212

Недавно я опубликовал вопрос о синтаксической версии 2.0 относительно определения share. У меня было это работает в GHC 7.6 :

{-# LANGUAGE GADTs, TypeOperators, FlexibleContexts #-}

import Data.Syntactic
import Data.Syntactic.Sugar.BindingT

data Let a where
    Let :: Let (a :-> (a -> b) :-> Full b)

share :: (Let :<: sup,
          sup ~ Domain b, sup ~ Domain a,
          Syntactic a, Syntactic b,
          Syntactic (a -> b),
          SyntacticN (a -> (a -> b) -> b) 
                     fi)
           => a -> (a -> b) -> b
share = sugarSym Let

Однако GHC 7.8 хочет -XAllowAmbiguousTypesскомпилировать эту подпись. С другой стороны , я могу заменить fiс

(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))

это тип, на который ссылается fundep SyntacticN. Это позволяет мне избежать расширения. Конечно это

  • очень длинный тип для добавления к уже большой подписи
  • утомительно выводить вручную
  • ненужный из-за fundep

Мои вопросы:

  1. Это приемлемое использование -XAllowAmbiguousTypes?
  2. В общем, когда следует использовать это расширение? Ответ здесь предполагает, что «это почти никогда не хорошая идея».
  3. Несмотря на то, что я прочитал документы , мне все еще трудно решить, является ли ограничение двусмысленным или нет. В частности, рассмотрим эту функцию из Data.Syntactic.Sugar:

    sugarSym :: (sub :<: AST sup, ApplySym sig fi sup, SyntacticN f fi) 
             => sub sig -> f
    sugarSym = sugarN . appSym

    Мне кажется, что fi(и, возможно, sup) здесь должно быть неоднозначным, но он компилируется без расширения. Почему sugarSymоднозначно пока shareесть? Поскольку shareэто приложение sugarSym, shareвсе ограничения исходят прямо из sugarSym.


4
Есть ли какая-то причина, по которой вы не можете просто использовать выведенный тип для sugarSym Let, который включает (SyntacticN f (ASTF sup a -> ASTF sup (a -> b) -> ASTF sup b), Let :<: sup) => fи не включает в себя переменные неоднозначного типа?
Космикус

3
@kosmikus Соррт потребовалось так много времени, чтобы ответить. Этот код не компилируется с выведенной для подписи share, но делает компиляцию , когда - либо из подписей , указанных в вопросе используется.
Ваш

3
Неопределенное поведение, вероятно, не самый подходящий термин. Это трудно понять, основываясь только на одной программе. Проблема заключается в том, что GHCI не может доказать типы в вашей программе. Есть долгая дискуссия, которая может заинтересовать вас только по этой теме. haskell.org/pipermail/haskell-cafe/2008-April/041397.html
BlamKiwi

6
Что касается (3), этот тип не является неоднозначным из-за функциональных зависимостей в определении SyntacticN (т.е. f -> fi) и ApplySym (в частности, fi -> sig, sup). От того, вы получите , что fсамо по себе достаточно , чтобы полностью устранить неоднозначность sig, fiи sup.
user2141650

3
@ user2141650 Извините, что так долго не отвечал. Вы говорите, что fundep на SyntacticNделает fiоднозначным в sugarSym, но тогда почему то же самое не относится к fiв share?
crockeea

Ответы:


12

Я не вижу ни одной опубликованной версии синтаксиса, подпись которой sugarSymиспользует эти точные имена типов, поэтому я буду использовать ветку разработки на коммите 8cfd02 ^ , последней версии, которая все еще использовала эти имена.

Итак, почему GHC жалуется на fiподпись в вашем типе, а не на подпись sugarSym? В документации, на которую вы ссылаетесь, объясняется, что тип является неоднозначным, если он не отображается справа от ограничения, если только ограничение не использует функциональные зависимости, чтобы вывести неоднозначный в противном случае тип из других не неоднозначных типов. Итак, давайте сравним контексты двух функций и поищем функциональные зависимости.

class ApplySym sig f sym | sig sym -> f, f -> sig sym
class SyntacticN f internal | f -> internal

sugarSym :: ( sub :<: AST sup
            , ApplySym sig fi sup
            , SyntacticN f fi
            ) 
         => sub sig -> f

share :: ( Let :<: sup
         , sup ~ Domain b
         , sup ~ Domain a
         , Syntactic a
         , Syntactic b
         , Syntactic (a -> b)
         , SyntacticN (a -> (a -> b) -> b) fi
         )
      => a -> (a -> b) -> b

Так sugarSym, не-неоднозначные типы sub, sigи f, и от тех , что мы должны быть в состоянии следовать функциональным зависимостям, чтобы неоднозначность всех других типов , используемых в контексте, а именно supи fi. И действительно, f -> internalфункциональная зависимость в SyntacticNиспользует нашу, fчтобы устранить неоднозначность fi, и после этого f -> sig symфункциональная зависимость ApplySymиспользует нашу недавно устраненную неоднозначность fiдля устранения неоднозначности supsig, которая уже была неоднозначной). Это объясняет, почему sugarSymне требуется AllowAmbiguousTypesрасширение.

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

Overlapping instances for SyntacticN b fi
  arising from the ambiguity check for share
Matching givens (or their superclasses):
  (SyntacticN (a -> (a -> b) -> b) fi1)
Matching instances:
  instance [overlap ok] (Syntactic f, Domain f ~ sym,
                         fi ~ AST sym (Full (Internal f))) =>
                        SyntacticN f fi
    -- Defined in ‘Data.Syntactic.Sugar’
  instance [overlap ok] (Syntactic a, Domain a ~ sym,
                         ia ~ Internal a, SyntacticN f fi) =>
                        SyntacticN (a -> f) (AST sym (Full ia) -> fi)
    -- Defined in ‘Data.Syntactic.Sugar’
(The choice depends on the instantiation of b, fi’)
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes

Поэтому, если я правильно понял, это не значит, что GHC думает, что ваши типы неоднозначны, а скорее, что при проверке, являются ли ваши типы неоднозначными, GHC столкнулся с другой, отдельной проблемой. Затем он говорит вам, что если бы вы сказали GHC не выполнять проверку неоднозначности, он бы не столкнулся с этой отдельной проблемой. Это объясняет, почему включение AllowAmbiguousTypes позволяет вашему коду компилироваться.

Однако проблема с перекрывающимися экземплярами остается. Два экземпляра, перечисленные GHC ( SyntacticN f fiи SyntacticN (a -> f) ...), перекрывают друг друга. Как ни странно, первый из них должен совпадать с любым другим экземпляром, что является подозрительным. И что это [overlap ok]значит?

Я подозреваю, что синтаксический компилируется с OverlappingInstances. И, глядя на код , это действительно так.

Немного поэкспериментировав, кажется, что GHC в порядке с перекрывающимися экземплярами, когда становится ясно, что одно строго более общее, чем другое:

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Foo a where
  whichOne :: a -> String

instance Foo a where
  whichOne _ = "a"

instance Foo [a] where
  whichOne _ = "[a]"

-- |
-- >>> main
-- [a]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])

Но GHC не подходит для перекрывающихся случаев, когда ни один из них явно не подходит лучше, чем другой:

{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}

class Foo a where
  whichOne :: a -> String

instance Foo (f Int) where  -- this is the line which changed
  whichOne _ = "f Int"

instance Foo [a] where
  whichOne _ = "[a]"

-- |
-- >>> main
-- Error: Overlapping instances for Foo [Int]
main :: IO ()
main = putStrLn $ whichOne (undefined :: [Int])

Ваша подпись типа использует SyntacticN (a -> (a -> b) -> b) fi, и ни то, SyntacticN f fiни другое не SyntacticN (a -> f) (AST sym (Full ia) -> fi)подходит лучше, чем другие. Если я изменю эту часть подписи вашего типа на SyntacticN a fiили SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi), GHC больше не будет жаловаться на совпадение.

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


2

Я обнаружил, что AllowAmbiguousTypesэто очень удобно для использования с TypeApplications. Рассмотрим функцию natVal :: forall n proxy . KnownNat n => proxy n -> Integerиз GHC.TypeLits .

Чтобы использовать эту функцию, я мог бы написать natVal (Proxy::Proxy5). Альтернативный стиль для использования TypeApplications: natVal @5 Proxy. Тип Proxyвыводится приложением типа, и раздражает необходимость писать его каждый раз, когда вы вызываете natVal. Таким образом, мы можем включить AmbiguousTypesи написать:

{-# Language AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-}

ambiguousNatVal :: forall n . (KnownNat n) => Integer
ambiguousNatVal = natVal @n Proxy

five = ambiguousNatVal @5 -- no `Proxy ` needed!

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.