Oto scenariusz: napisałem trochę kodu z podpisem typu, a skargi GHC nie mogły wydedukować x ~ y dla niektórych x
i y
. Zwykle możesz rzucić GHC kość i po prostu dodać izomorfizm do ograniczeń funkcji, ale jest to zły pomysł z kilku powodów:
- Nie podkreśla zrozumienia kodu.
- Możesz skończyć z 5 ograniczeniami, w których wystarczyłoby (na przykład, jeśli 5 jest implikowanych przez jedno bardziej szczegółowe ograniczenie)
- Możesz skończyć z fałszywymi ograniczeniami, jeśli zrobiłeś coś złego lub jeśli GHC jest nieprzydatny
Właśnie spędziłem kilka godzin walcząc ze sprawą 3. Bawię się syntactic-2.0
i próbowałem zdefiniować wersję niezależną od domeny share
, podobną do wersji zdefiniowanej w NanoFeldspar.hs
.
Miałem to:
{-# LANGUAGE GADTs, FlexibleContexts, TypeOperators #-}
import Data.Syntactic
-- Based on NanoFeldspar.hs
data Let a where
Let :: Let (a :-> (a -> b) :-> Full b)
share :: (Let :<: sup,
Domain a ~ sup,
Domain b ~ sup,
SyntacticN (a -> (a -> b) -> b) fi)
=> a -> (a -> b) -> a
share = sugarSym Let
i GHC could not deduce (Internal a) ~ (Internal b)
, co z pewnością nie było tym, do czego dążyłem. Więc albo napisałem kod, którego nie zamierzałem (co wymagało ograniczenia), albo GHC chciało tego ograniczenia z powodu innych ograniczeń, które napisałem.
Okazuje się, że musiałem dodać (Syntactic a, Syntactic b, Syntactic (a->b))
do listy ograniczeń, z których żaden nie sugeruje (Internal a) ~ (Internal b)
. Zasadniczo natknąłem się na właściwe ograniczenia; Nadal nie mam systematycznego sposobu na ich znalezienie.
Moje pytania to:
- Dlaczego GHC zaproponowało to ograniczenie? Nigdzie w składni nie ma ograniczenia
Internal a ~ Internal b
, więc skąd GHC to wzięło? - Zasadniczo, jakich technik można użyć do prześledzenia źródła ograniczenia, które według GHC jest potrzebne? Nawet dla ograniczeń, że można dowiedzieć się, moje podejście jest zasadniczo brute zmusza ścieżkę przestępstwa przez fizycznie spisując cyklicznych ograniczeń. To podejście polega zasadniczo na zejściu nieskończonej króliczej dziury ograniczeń i dotyczy mniej efektywnej metody, jaką mogę sobie wyobrazić.
źródło
a
ib
są powiązane - spójrz na podpis typu poza swoim kontekstem -a -> (a -> b) -> a
niea -> (a -> b) -> b
. Może to jest to? Dzięki rozwiązaniom ograniczającym mogą one wpływać na równość przechodnią w dowolnym miejscu , ale błędy zwykle pokazują lokalizację „blisko” miejsca, w którym wywołano ograniczenie. Byłoby fajnie choć @jozefg - może oznaczać ograniczenia tagami lub czymś innym, aby pokazać, skąd się wzięły? : sOdpowiedzi:
Po pierwsze, twoja funkcja ma zły typ; Jestem pewien, że tak powinno być (bez kontekstu)
a -> (a -> b) -> b
. GHC 7.10 jest nieco bardziej pomocny w zaznaczeniu tego, ponieważ w twoim oryginalnym kodzie narzeka na brakujące ograniczenieInternal (a -> b) ~ (Internal a -> Internal a)
. Po naprawieniushare
typu GHC 7.10 pozostaje pomocny w prowadzeniu nas:Could not deduce (Internal (a -> b) ~ (Internal a -> Internal b))
Po dodaniu powyższego otrzymujemy
Could not deduce (sup ~ Domain (a -> b))
Po dodaniu, że możemy dostać
Could not deduce (Syntactic a)
,Could not deduce (Syntactic b)
aCould not deduce (Syntactic (a -> b))
Po dodaniu tych trzech ostatecznie sprawdza typ; więc kończymy
Więc powiedziałbym, że GHC nie był bezużyteczny w prowadzeniu nas.
Jeśli chodzi o pytanie dotyczące śledzenia, skąd GHC pobiera wymagania dotyczące ograniczenia, możesz w szczególności wypróbować flagi debugowania GHC
-ddump-tc-trace
, a następnie przeczytać wynikowy dziennik, aby zobaczyć, gdzieInternal (a -> b) ~ t
i(Internal a -> Internal a) ~ t
są dodane doWanted
zestawu, ale będzie to dość długi odczyt .źródło
Czy próbowałeś tego w GHC 8.8+?
Kluczem jest użycie dziury typu między ograniczeniami:
_ => your difficult type
źródło