Коли -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))

який тип має на увазі фонд SyntacticN. Це дозволяє мені уникати розширення. Звичайно, це так

  • дуже довгий тип, який потрібно додати до вже великого підпису
  • втомлює виводити вручну
  • непотрібні через фундуп

Мої запитання:

  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і не включає неоднозначні змінні типу?
kosmikus

3
@kosmikus Sorrt потрібно було так довго, щоб відповісти. Цей код НЕ компілюється з виведеною для підпису 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.
користувач2141650

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]означає?

Я підозрюю, що Syntactic складено з OverlappingInsances. А дивлячись на код , справді це так і є.

Експериментуючи трохи, здається, що 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.