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.

  1. Alias ​​de type
  2. Types de syndicats discriminés
  3. Types d'enregistrement
  4. Types d'interface
  5. Types de classes
  6. 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


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow