Automatyczna generalizacja
Język F# używa wnioskowania typu do oceny typów funkcji i wyrażeń. W tym temacie opisano, jak język F# automatycznie uogólnia argumenty i typy funkcji, tak aby działały z wieloma typami, gdy jest to możliwe.
Automatyczna generalizacja
Kompilator języka F# podczas wykonywania wnioskowania typu w funkcji określa, czy dany parametr może być ogólny. Kompilator sprawdza każdy parametr i określa, czy funkcja ma zależność od określonego typu tego parametru. Jeśli tak nie jest, typ jest wnioskowany jako ogólny.
Poniższy przykład kodu ilustruje funkcję, którą kompilator wywnioskuje jako ogólną.
let max a b = if a > b then a else b
Typ jest wnioskowany jako 'a -> 'a -> 'a
.
Typ wskazuje, że jest to funkcja, która przyjmuje dwa argumenty tego samego nieznanego typu i zwraca wartość tego samego typu. Jedną z przyczyn, dla których poprzednia funkcja może być ogólna, jest to, że operator większy niż (>
) jest sam w sobie ogólny. Operator greater-than ma podpis 'a -> 'a -> bool
. Nie wszystkie operatory są ogólne, a jeśli kod w funkcji używa typu parametru razem z niegeneryjną funkcją lub operatorem, nie można uogólnić tego typu parametru.
Ponieważ max
jest ogólny, można go używać z typami, takimi jak int
, float
i tak dalej, jak pokazano w poniższych przykładach.
let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3
Jednak dwa argumenty muszą być tego samego typu. Podpis to 'a -> 'a -> 'a
, a nie 'a -> 'b -> 'a
. W związku z tym poniższy kod generuje błąd, ponieważ typy nie są zgodne.
// Error: type mismatch.
let biggestIntFloat = max 2.0 3
Funkcja max
działa również z dowolnym typem obsługującym operator greater-than. W związku z tym można go również użyć w ciągu, jak pokazano w poniższym kodzie.
let testString = max "cab" "cat"
Ograniczenie wartości
Kompilator wykonuje automatyczną uogólnienie tylko na pełnych definicjach funkcji, które mają jawne argumenty i na prostych wartościach niezmiennych.
Oznacza to, że kompilator zgłasza błąd, jeśli spróbujesz skompilować kod, który nie jest wystarczająco ograniczony, aby był określonym typem, ale nie można go również uogólnić. Komunikat o błędzie dla tego problemu odnosi się do tego ograniczenia automatycznego uogólniania wartości jako ograniczenia wartości.
Zazwyczaj błąd ograniczenia wartości występuje, gdy konstrukcja ma być ogólna, ale kompilator ma niewystarczające informacje do uogólnienia lub gdy przypadkowo pominięto wystarczające informacje o typie w konstrukcji niegenerowanej. Rozwiązaniem błędu ograniczenia wartości jest podanie bardziej wyraźnych informacji w celu bardziej pełnego ograniczenia problemu wnioskowania typu w jeden z następujących sposobów:
Ogranicz typ, który ma być niegenerowany, dodając jawną adnotację typu do wartości lub parametru.
Jeśli problem używa konstrukcji niegeneralizowalnej do zdefiniowania funkcji ogólnej, takiej jak kompozycja funkcji lub nieukończono zastosowane argumenty funkcji curried, spróbuj ponownie napisać funkcję jako zwykłą definicję funkcji.
Jeśli problem jest wyrażeniem zbyt złożonym, które ma być uogólnione, wprowadź je w funkcję, dodając dodatkowy, nieużywany parametr.
Dodaj jawne parametry typu ogólnego. Ta opcja jest rzadko używana.
Poniższe przykłady kodu ilustrują każdy z tych scenariuszy.
Przypadek 1. Zbyt złożone wyrażenie. W tym przykładzie lista counter
ma być int option ref
wartością , ale nie jest zdefiniowana jako prosta niezmienna wartość.
let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None
Przypadek 2: Użycie konstrukcji niegeneralizowalnej do zdefiniowania funkcji ogólnej. W tym przykładzie konstrukcja jest niegenerowana, ponieważ obejmuje częściowe stosowanie argumentów funkcji.
let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj
Przypadek 3: Dodanie dodatkowego, nieużywanego parametru. Ponieważ to wyrażenie nie jest wystarczająco proste w przypadku uogólniania, kompilator wystawia błąd ograniczenia wartości.
let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []
Przypadek 4. Dodawanie parametrów typu.
let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)
W ostatnim przypadku wartość staje się funkcją typu, która może służyć do tworzenia wartości wielu różnych typów, na przykład w następujący sposób:
let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>