Recherche…
Introduction aux types
Les types peuvent représenter différents types de choses. Il peut s'agir d'une seule donnée, d'un ensemble de données ou d'une fonction.
En F #, nous pouvons regrouper les types en deux catégories .:
Types F #:
// Functions let a = fun c -> c // Tuples let b = (0, "Foo") // Unit type let c = ignore // Records type r = { Name : string; Age : int } let d = { Name = "Foo"; Age = 10 } // Discriminated Unions type du = | Foo | Bar let e = Bar // List and seq let f = [ 0..10 ] let g = seq { 0..10 } // Aliases type MyAlias = string
Types .NET
- Type intégré (int, bool, string, ...)
- Classes, structures et interfaces
- Les délégués
- Tableaux
Abréviations de type
Les abréviations de type vous permettent de créer des alias sur des types existants pour leur donner un sens plus significatif.
// Name is an alias for a string
type Name = string
// PhoneNumber is an alias for a string
type PhoneNumber = string
Ensuite, vous pouvez utiliser l'alias comme n'importe quel autre type:
// Create a record type with the alias
type Contact = {
Name : Name
Phone : PhoneNumber }
// Create a record instance
// We can assign a string since Name and PhoneNumber are just aliases on string type
let c = {
Name = "Foo"
Phone = "00 000 000" }
printfn "%A" c
// Output
// {Name = "Foo";
// Phone = "00 000 000";}
Attention, les alias ne vérifient pas la cohérence des types. Cela signifie que deux alias ciblant le même type peuvent être affectés l'un à l'autre:
let c = {
Name = "Foo"
Phone = "00 000 000" }
let d = {
Name = c.Phone
Phone = c.Name }
printfn "%A" d
// Output
// {Name = "00 000 000";
// Phone = "Foo";}
Les types sont créés en F # en utilisant le mot-clé type
F#
utilise le mot-clé type
pour créer différents types de types.
- Alias de type
- Types de syndicats discriminés
- Types d'enregistrement
- Types d'interface
- Types de classes
- Types de structures
Exemples avec code C#
équivalent lorsque possible:
// Equivalent C#:
// using IntAliasType = System.Int32;
type IntAliasType = int // As in C# this doesn't create a new type, merely an alias
type DiscriminatedUnionType =
| FirstCase
| SecondCase of int*string
member x.SomeProperty = // We can add members to DU:s
match x with
| FirstCase -> 0
| SecondCase (i, _) -> i
type RecordType =
{
Id : int
Name : string
}
static member New id name : RecordType = // We can add members to records
{ Id = id; Name = name } // { ... } syntax used to create records
// Equivalent C#:
// interface InterfaceType
// {
// int Id { get; }
// string Name { get; }
// int Increment (int i);
// }
type InterfaceType =
interface // In order to create an interface type, can also use [<Interface>] attribute
abstract member Id : int
abstract member Name : string
abstract member Increment : int -> int
end
// Equivalent C#:
// class ClassType : InterfaceType
// {
// static int increment (int i)
// {
// return i + 1;
// }
//
// public ClassType (int id, string name)
// {
// Id = id ;
// Name = name ;
// }
//
// public int Id { get; private set; }
// public string Name { get; private set; }
// public int Increment (int i)
// {
// return increment (i);
// }
// }
type ClassType (id : int, name : string) = // a class type requires a primary constructor
let increment i = i + 1 // Private helper functions
interface InterfaceType with // Implements InterfaceType
member x.Id = id
member x.Name = name
member x.Increment i = increment i
// Equivalent C#:
// class SubClassType : ClassType
// {
// public SubClassType (int id, string name) : base(id, name)
// {
// }
// }
type SubClassType (id : int, name : string) =
inherit ClassType (id, name) // Inherits ClassType
// Equivalent C#:
// struct StructType
// {
// public StructType (int id)
// {
// Id = id;
// }
//
// public int Id { get; private set; }
// }
type StructType (id : int) =
struct // In order create a struct type, can also use [<Struct>] attribute
member x.Id = id
end
Type Inférence
Reconnaissance
Cet exemple est adapté de cet article sur l' inférence de type
Qu'est-ce que le type Inference?
Type Inference est le mécanisme qui permet au compilateur de déduire quels types sont utilisés et où. Ce mécanisme repose sur un algorithme souvent appelé «Hindley-Milner» ou «HM». Voir ci-dessous quelques règles pour déterminer les types de valeurs simples et fonctionnelles:
- Regardez les littéraux
- Regardez les fonctions et autres valeurs avec lesquelles quelque chose interagit
- Regardez toutes les contraintes de type explicites
- S'il n'y a aucune contrainte, généraliser automatiquement aux types génériques
Regardez les littéraux
Le compilateur peut déduire des types en regardant les littéraux. Si le littéral est un int et que vous y ajoutez «x», alors «x» doit également être un int. Mais si le littéral est un flottant et que vous y ajoutez «x», alors «x» doit également être un flottant.
Voici quelques exemples:
let inferInt x = x + 1
let inferFloat x = x + 1.0
let inferDecimal x = x + 1m // m suffix means decimal
let inferSByte x = x + 1y // y suffix means signed byte
let inferChar x = x + 'a' // a char
let inferString x = x + "my string"
Regardez les fonctions et les autres valeurs avec lesquelles il interagit
S'il n'y a aucun littéral, le compilateur essaie de déterminer les types en analysant les fonctions et les autres valeurs avec lesquelles ils interagissent.
let inferInt x = x + 1
let inferIndirectInt x = inferInt x //deduce that x is an int
let inferFloat x = x + 1.0
let inferIndirectFloat x = inferFloat x //deduce that x is a float
let x = 1
let y = x //deduce that y is also an int
Examinez les contraintes de type explicites ou les annotations
Si des contraintes de type explicites ou des annotations sont spécifiées, le compilateur les utilisera.
let inferInt2 (x:int) = x // Take int as parameter
let inferIndirectInt2 x = inferInt2 x // Deduce from previous that x is int
let inferFloat2 (x:float) = x // Take float as parameter
let inferIndirectFloat2 x = inferFloat2 x // Deduce from previous that x is float
Généralisation automatique
Si après tout cela, il n'y a pas de contraintes trouvées, le compilateur ne fait que rendre les types génériques.
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
Des choses qui peuvent aller mal avec l'inférence de type
L'inférence de type n'est pas parfaite, hélas. Parfois, le compilateur ne sait pas quoi faire. Encore une fois, comprendre ce qui se passe vous aidera vraiment à rester calme au lieu de vouloir tuer le compilateur. Voici quelques-unes des principales raisons des erreurs de type:
- Déclarations hors d'usage
- Pas suffisamment d'informations
- Méthodes surchargées
Déclarations hors d'usage
Une règle de base est que vous devez déclarer les fonctions avant leur utilisation.
Ce code échoue:
let square2 x = square x // fails: square not defined
let square x = x * x
Mais c'est ok:
let square x = x * x
let square2 x = square x // square already defined earlier
Déclarations récursives ou simultanées
Une variante du problème «hors service» se produit avec les fonctions récursives ou les définitions qui doivent se référer. Aucune réorganisation ne sera utile dans ce cas - nous devons utiliser des mots-clés supplémentaires pour aider le compilateur.
Lorsqu'une fonction est en cours de compilation, l'identifiant de la fonction n'est pas disponible pour le corps. Donc, si vous définissez une fonction récursive simple, vous obtiendrez une erreur de compilation. La solution consiste à ajouter le mot-clé «rec» dans la définition de la fonction. Par exemple:
// the compiler does not know what "fib" means
let fib n =
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
// error FS0039: The value or constructor 'fib' is not defined
Voici la version fixe avec «rec fib» ajouté pour indiquer qu'il est récursif:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
Pas suffisamment d'informations
Parfois, le compilateur n'a pas assez d'informations pour déterminer un type. Dans l'exemple suivant, le compilateur ne sait pas sur quel type la méthode Length est censée fonctionner. Mais il ne peut pas non plus être générique, alors il se plaint.
let stringLength s = s.Length
// error FS0072: Lookup on object of indeterminate type
// based on information prior to this program point.
// A type annotation may be needed ...
Ces types d'erreur peuvent être corrigés avec des annotations explicites.
let stringLength (s:string) = s.Length
Méthodes surchargées
Lorsque vous appelez une classe ou une méthode externe dans .NET, vous obtenez souvent des erreurs dues à une surcharge.
Dans de nombreux cas, comme l'exemple ci-dessous, vous devrez annoter explicitement les paramètres de la fonction externe afin que le compilateur sache quelle méthode surchargée appeler.
let concat x = System.String.Concat(x) //fails
let concat (x:string) = System.String.Concat(x) //works
let concat x = System.String.Concat(x:string) //works
Parfois, les méthodes surchargées ont des noms d'argument différents, auquel cas vous pouvez également donner un indice au compilateur en nommant les arguments. Voici un exemple pour le constructeur StreamReader.
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works