Was bedeutet es, eine Einschränkung in den Typ eines Arguments anstelle des Funktionstyps einzufügen?

8

Ich habe den Typ des Arguments einer Funktion eingeschränkt, anstatt den Typ der Funktion einzugeben.
Ich dachte, dies würde entweder einen Syntaxfehler verursachen oder dem Typ der Funktion weitere Informationen hinzufügen.
Es sieht jedoch so aus, als würde die Einschränkung vollständig ignoriert.

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}

test :: a -> String
test (n :: (Num a, Ord a) => a) =
    if n > 10 then "Hello"
    else "World"

main = print "Hello World"

Dies ergibt den folgenden Typfehler:

Test3.hs:6:8: error:
     No instance for (Num a) arising from a use of n
      Possible fix:
        add (Num a) to the context of
          the type signature for:
            test :: forall a. a -> String
     In the first argument of ‘(>)’, namely n
      In the expression: n > 10
      In the expression: if n > 10 then "Hello" else "World"
  |
6 |     if n > 10 then "Hello"
  |        ^

Test3.hs:6:8: error:
     No instance for (Ord a) arising from a use of ‘>’
      Possible fix:
        add (Ord a) to the context of
          the type signature for:
            test :: forall a. a -> String
     In the expression: n > 10
      In the expression: if n > 10 then "Hello" else "World"
      In an equation for test’:
          test (n :: (Num a, Ord a) => a)
            = if n > 10 then "Hello" else "World"
  |
6 |     if n > 10 then "Hello"
  |  

Was bedeutet es eigentlich, eine Einschränkung in den Typ des Arguments zu setzen?

BEARBEITEN:

Warum muss dies RankNTypeserweitert werden?
Es wird nicht benötigt, wenn ich (Num a, Ord a) =>Einschränkungen entferne .

happycoder97
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Samuel Liew

Antworten:

7

Dies ist eine ziemlich exotische Instanz der polymorphen Subsumtion, wie hier beschrieben , die mit der Constraint-Subsumtion interagiert.

Wenn ein Typ asubsumiert b, exp :: aimpliziert dies exp :: bin der Oberflächensprache. Ein besonderes Beispiel für eine Subsumtion ist f :: forall a. a -> adies f :: Int -> Int. Wir haben auch n :: IntImplikationen n :: c => Intfür jede cEinschränkung.

In der Kernsprache gibt es jedoch überhaupt keine Subsumtion. Jeder Fall der Subsumtion in der Oberflächensprache muss in explizite Lambdas und Anwendungen übersetzt werden. Auch wird c => aeinfach c -> a, und die Verwendung von eingeschränkten Funktionen wird in einfache Funktionsanwendung von f :: c => aeinigen übersetzt inst :: c. Daher f :: forall a. a -> awird f @Int :: Int -> Intund n :: Intwird \_ -> n :: c -> Int.

Ein selten verwendeter Fall ist die kontravariante Subsumtionsregel für Funktionen. Folgendes ist gültiger Code:

f :: (Int -> Int) -> Bool
f _ = True

g :: (forall a. a -> a) -> Bool
g = f

Dies wird übersetzt in

f :: (Int -> Int) -> Bool
f = \_ -> True

g :: (forall a. a -> a) -> Bool
g = \x -> f (x @Int)

Ähnlich funktioniert es mit der Subsumtion von Einschränkungen:

f :: forall a. (Eq a => a) -> Bool
f _ = True

g :: forall a . a -> Bool
g = f

Welches ist übersetzt in

f :: forall a. (Eq a -> a) -> Bool
f = \_ -> True

g :: forall a . a -> Bool
g = \x -> f (\_ -> x)

Näher an der ursprünglichen Frage, wenn wir haben

f (x :: Eq a => a) = True

als Top-Definition ist sein abgeleiteter Typ forall a. (Eq a => a) -> Bool. Wir können jedoch jede Typanmerkung haben, für fdie der abgeleitete Typ subsumiert wird! So können wir haben:

f :: forall a. a -> Bool
f (x :: Eq a => a) = True

Und GHC ist immer noch glücklich. Der ursprüngliche Code

test :: a -> String
test (n :: (Num a, Ord a) => a) =
    if n > 10 then "Hello"
    else "World"

entspricht der etwas klareren folgenden Version:

test :: forall a. a -> String
test (n :: (Num a, Ord a) => a) =
    if n > 10 then "Hello"
    else "World"

Der Typfehler, den Sie erhalten, ist einfach, weil nes sich tatsächlich um eine Funktion mit zwei Argumenten handelt, von denen eines den Typ Num aund das andere Ord ahat und beide Argumente Datensätze Numund OrdMethoden enthalten. Da die Definition jedoch keine solchen Instanzen enthält, können Sie sie nicht nals Zahl verwenden. Die Übersetzung würde n > 10nach (>) inst (n inst) (10 inst), wo konvertieren inst :: Num a, aber es gibt keine solche inst, so dass wir nicht übersetzen können.

Daher wird im Hauptteil des testCodes noch geprüft n :: (Num a, Ord a) => a). Wenn wir jedoch nur "Hallo" zurückgeben, ohne es zu verwenden , erhalten wir nähnlich wie im vorherigen fFall einen abgeleiteten Typ, der den forall a. a -> StringAnnotationstyp subsumiert . Die Subsumtion wird dann in der Übersetzungsausgabe realisiert, indem jedes Vorkommen nim Körper von testmit ersetzt wird \_ -> n. Da ndies aber im Körper nicht vorkommt, macht die Übersetzung hier nichts.

András Kovács
quelle