Kiedy-XAllowAmbiguousTypes jest odpowiedni?

Ostatnio zamieściłem pytanie o składni-2.0 dotyczące definicji share. Miałem to działa w 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
Jednak GHC 7.8 chce -XAllowAmbiguousTypes skompilować się z tym podpisem. Alternatywnie mogę zastąpić fi
(ASTF sup (Internal a) -> AST sup ((Internal a) :-> Full (Internal b)) -> ASTF sup (Internal b))

, który jest typem sugerowanym przez fundep na SyntacticN. To pozwala mi uniknąć przedłużenia. Oczywiście to jest

  • bardzo długi typ do dodania do już dużego podpis
  • męczące ręczne wyprowadzanie
  • niepotrzebne ze względu na fundep

Moje pytania to:

  1. czy jest to dopuszczalne użycie -XAllowAmbiguousTypes?
  2. ogólnie, kiedy należy stosować to rozszerzenie? Odpowiedź tutaj sugeruje "to prawie nigdy nie jest dobry pomysł".
  3. Chociaż czytałem dokumenty , nadal mam problem z podjęciem decyzji, czy ograniczenie jest niejednoznaczne, czy nie. W szczególności rozważmy tę funkcję z Data.Składniowe."Suggested": {]}

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

    Wydaje mi się, że fi (i prawdopodobnie sup) powinno być tu niejednoznaczne, ale kompiluje się bez rozszerzenia. Dlaczego sugarSym jest jednoznaczne, podczas gdy share jest? Ponieważ share jest aplikacją sugarSym, ograniczenia share pochodzą wprost z sugarSym.

 210
Author: Community, 2014-05-15

2 answers

Nie widzę żadnej opublikowanej wersji składni, której podpis dla sugarSym używa dokładnie tych nazw typów, więc będę używał gałęzi deweloperskiej w commit 8cfd02^, ostatniej wersji, która nadal używała tych nazw.

Więc dlaczego GHC narzeka na fi w podpisie typu, ale nie na sugarSym? Dołączona dokumentacja wyjaśnia, że typ jest niejednoznaczny, jeśli nie pojawia się po prawej stronie ograniczenia, chyba że ograniczenie używa funkcji zależności pozwalające wywnioskować Typ inaczej niejednoznaczny z innych typów niejednoznacznych. Porównajmy więc konteksty obu funkcji i poszukajmy zależności funkcyjnych.

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

Więc dla sugarSym, typy niejednoznaczne są sub, sig i f, a z nich powinniśmy być w stanie podążać za zależnościami funkcyjnymi w celu disambiguate wszystkich innych typów używanych w kontekście, mianowicie sup i fi. I rzeczywiście, f -> internal zależność funkcjonalna w SyntacticN używa naszego f do disambiguate our fi, a następnie f -> sig sym functional dependency in ApplySym używa nowo-disambiguated {[5] } do disambiguate sup (i sig, co było już niejednoznaczne). To wyjaśnia, dlaczego sugarSym nie wymaga rozszerzenia AllowAmbiguousTypes.

Spójrzmy teraz na sugar. Pierwszą rzeczą, którą zauważyłem, jest to, że kompilator nie skarży się na niejednoznaczny Typ, ale raczej na nakładające się instancje:

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

Więc jeśli dobrze to czytam, to nie jest to, że GHC uważa, że Twoje typy są niejednoznaczne, ale raczej, podczas sprawdzania, czy Twoje typy są niejednoznaczne, GHC napotkało inny, oddzielny problem. Następnie mówi ci, że gdybyś powiedział GHC, aby nie przeprowadzał kontroli dwuznaczności, nie napotkałby tego oddzielnego problemu. To wyjaśnia, dlaczego włączenie AllowAmbiguousTypes umożliwia kompilację kodu.

Jednak problem z nakładającymi się instancjami pozostaje. Dwie instancje wymienione przez GHC (SyntacticN f fi i SyntacticN (a -> f) ...) nakładają się na siebie ze sobą. Co dziwne, wydaje się, że pierwsza z nich powinna pokrywać się z każdym innym przypadkiem, co jest podejrzane. A co to znaczy?

Podejrzewam, że składnia jest kompilowana z OverlappingInstances. I patrząc na kod , rzeczywiście tak jest.

Wydaje się, że GHC jest w porządku z nakładającymi się przypadkami, gdy jest jasne, że jeden jest ściśle bardziej ogólny niż drugi:]}
{-# 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])

Ale GHC nie jest w porządku z nakładaniem się przykłady, gdy żadna z nich nie jest wyraźnie lepiej dopasowana od drugiej:

{-# 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])

Twój podpis typu używa SyntacticN (a -> (a -> b) -> b) fi, i ani SyntacticN f fi, ani SyntacticN (a -> f) (AST sym (Full ia) -> fi) nie pasują lepiej niż inne. Jeśli zmienię tę część twojego podpisu na SyntacticN a fi lub SyntacticN (a -> (a -> b) -> b) (AST sym (Full ia) -> fi), GHC nie będzie już narzekać na nakładanie się.

Na Twoim miejscu, przyjrzałbym się definicji tych dwóch możliwych instancji i ustalił, czy jedna z tych dwóch implementacji jest tą, którą chcesz.

 12
Author: gelisam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-23 13:40:52

Odkryłem, że AllowAmbiguousTypes jest bardzo wygodny w użyciu z TypeApplications. Rozważmy funkcję natVal :: forall n proxy . KnownNat n => proxy n -> Integer z GHC.TypeLits .

Aby skorzystać z tej funkcji, mogę napisać natVal (Proxy::Proxy5). Alternatywnym stylem jest użycie TypeApplications: natVal @5 Proxy. Typ Proxy jest wywnioskowany przez aplikację typu i denerwujące jest to, że musisz go pisać za każdym razem, gdy wywołujesz natVal. W ten sposób możemy włączyć AmbiguousTypes i napisać:

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

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

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

Jednak zauważ, że kiedy będziesz dwuznaczny, nie możesz wrócić !

 2
Author: crockeea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:35