web-dev-qa-db-fra.com

Différence en orme entre le type et l'alias de type?

Dans Elm, je ne peux pas savoir quand type est le mot clé approprié par rapport à type alias. La documentation ne semble pas avoir d'explication à cela, et je ne peux pas en trouver une dans les notes de version. Est-ce documenté quelque part?

89
ehdv

Comment j'y pense:

type est utilisé pour définir de nouveaux types d'unions:

type Thing = Something | SomethingElse

Avant cette définition, Something et SomethingElse ne signifiaient rien. Maintenant, ils sont tous les deux de type Thing, que nous venons de définir.

type alias est utilisé pour donner un nom à un autre type qui existe déjà:

type alias Location = { lat:Int, long:Int }

{ lat = 5, long = 10 } a le type { lat:Int, long:Int }, qui était déjà un type valide. Mais maintenant, nous pouvons également dire qu'il a le type Location car il s'agit d'un alias pour le même type.

Il convient de noter que ce qui suit compilera très bien et affichera "thing". Même si nous spécifions que thing est un String et aliasedStringIdentity prend un AliasedString, nous n'obtiendrons pas d'erreur indiquant qu'il existe une incompatibilité de type entre String/AliasedString:

import Graphics.Element exposing (show)

type alias AliasedString = String

aliasedStringIdentity: AliasedString -> AliasedString
aliasedStringIdentity s = s

thing : String
thing = "thing"

main =
  show <| aliasedStringIdentity thing
132
robertjlooby

La clé est le mot alias. Au cours de la programmation, quand on veut grouper des choses qui vont ensemble, on le met dans un disque, comme dans le cas d'un point

{ x = 5, y = 4 }  

ou un dossier étudiant.

{ name = "Billy Bob", grade = 10, classof = 1998 }

Maintenant, si vous aviez besoin de transmettre ces enregistrements, vous devez épeler le type entier, comme:

add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
  { a.x + b.x, a.y + b.y }

Si vous pouviez alias un point, la signature serait tellement plus facile à écrire!

type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
  { a.x + b.x, a.y + b.y }

Un alias est donc un raccourci pour autre chose. Ici, c'est un raccourci pour un type d'enregistrement. Vous pouvez le considérer comme donnant un nom à un type d'enregistrement que vous utiliserez souvent. C'est pourquoi on l'appelle un alias - c'est un autre nom pour le type d'enregistrement nu représenté par { x:Int, y:Int }

Alors que type résout un problème différent. Si vous venez de la POO, c'est le problème que vous résolvez avec l'héritage, la surcharge d'opérateur, etc. - parfois, vous voulez traiter les données comme une chose générique, et parfois vous voulez la traiter comme une chose spécifique.

Un lieu commun où cela se produit est lors de la transmission de messages - comme le système postal. Lorsque vous envoyez une lettre, vous souhaitez que le système postal traite tous les messages comme la même chose, vous n'avez donc à concevoir le système postal qu'une seule fois. Et en outre, le travail de routage du message doit être indépendant du message qu'il contient. Ce n'est que lorsque la lettre atteint sa destination que vous vous souciez de ce qu'est le message.

De la même manière, nous pourrions définir un type comme une union de tous les différents types de messages qui pourraient se produire. Imaginons que nous mettons en place un système de messagerie entre les étudiants et leurs parents. Il n'y a donc que deux messages que les collégiens peuvent envoyer: "J'ai besoin d'argent pour la bière" et "J'ai besoin d'un slip".

type MessageHome = NeedBeerMoney | NeedUnderpants

Alors maintenant, lorsque nous concevons le système de routage, les types de nos fonctions peuvent simplement faire circuler MessageHome, au lieu de se soucier de tous les différents types de messages qu'il pourrait être. Le système de routage s'en fiche. Il suffit de savoir que c'est un MessageHome. Ce n'est que lorsque le message atteint sa destination, le domicile des parents, que vous devez comprendre de quoi il s'agit.

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

Si vous connaissez l'architecture Elm, la fonction de mise à jour est une déclaration de cas géante, car c'est la destination de l'endroit où le message est acheminé, et donc traité. Et nous utilisons des types d'union pour avoir un seul type à traiter lors de la transmission du message, mais nous pouvons ensuite utiliser une instruction case pour déterminer exactement de quel message il s'agit, afin que nous puissions le traiter.

8
Wilhelm

Permettez-moi de compléter les réponses précédentes en me concentrant sur les cas d'utilisation et en fournissant un peu de contexte sur les fonctions et modules constructeurs.



Utilisation de type alias

  1. Créer un alias et une fonction constructeur pour un enregistrement
    C'est le cas d'utilisation le plus courant: vous pouvez définir un nom alternatif et une fonction constructeur pour un type particulier de format d'enregistrement.

    type alias Person =
        { name : String
        , age : Int
        }
    

    La définition de l'alias de type implique automatiquement la fonction constructeur suivante (pseudo-code):
    Person : String -> Int -> { name : String, age : Int }
    Cela peut être utile, par exemple lorsque vous souhaitez écrire un décodeur Json.

    personDecoder : Json.Decode.Decoder Person
    personDecoder =
        Json.Decode.map2 Person
            (Json.Decode.field "name" Json.Decode.String)
            (Json.Decode.field "age" Int)
    


  2. Spécifiez les champs obligatoires
    Ils l'appellent parfois "enregistrements extensibles", ce qui peut être trompeur. Cette syntaxe peut être utilisée pour spécifier que vous attendez un enregistrement avec des champs particuliers présents. Tel que:

    type alias NamedThing x =
        { x
            | name : String
        }
    
    showName : NamedThing x -> Html msg
    showName thing =
        Html.text thing.name
    

    Ensuite, vous pouvez utiliser la fonction ci-dessus comme ceci (par exemple à votre avis):

    let
        joe = { name = "Joe", age = 34 }
    in
        showName joe
    

    conférence de Richard Feldman sur ElmEurope 2017 peut fournir un aperçu supplémentaire du moment où ce style mérite d'être utilisé.

  3. Renommer des trucs
    Vous pourriez le faire, car les nouveaux noms pourraient fournir une signification supplémentaire plus tard dans votre code, comme dans cet exemple

    type alias Id = String
    
    type alias ElapsedTime = Time
    
    type SessionStatus
        = NotStarted
        | Active Id ElapsedTime
        | Finished Id
    

    Peut-être un meilleur exemple de ce type d'utilisation dans le noyau est Time .

  4. Ré-exposer un type d'un module différent
    Si vous écrivez un package (pas une application), vous devrez peut-être implémenter un type dans un module, peut-être dans un module interne (non exposé), mais vous souhaitez exposer le type à partir d'un autre (public ) module. Ou, vous pouvez également exposer votre type à partir de plusieurs modules.
    Task dans le noyau et Http.Request dans Http sont des exemples pour le premier, tandis que Json.Encode.Value et Json.Decode.Value paire est un exemple de la dernière.

    Vous ne pouvez le faire que si vous souhaitez autrement garder le type opaque: vous n'exposez pas les fonctions du constructeur. Pour plus de détails, voir les utilisations de type ci-dessous.

Il convient de noter que dans les exemples ci-dessus, seul # 1 fournit une fonction constructeur. Si vous exposez votre alias de type dans # 1 comme module Data exposing (Person) cela exposera le nom du type ainsi que la fonction constructeur.



Utilisation de type

  1. Définissez un type d'union balisé
    C'est le cas d'utilisation le plus courant, un bon exemple en est le type Maybe dans le noya :

    type Maybe a
        = Just a
        | Nothing
    

    Lorsque vous définissez un type, vous définissez également ses fonctions constructeur. Dans le cas de Peut-être que ce sont (pseudo-code):

    Just : a -> Maybe a
    
    Nothing : Maybe a
    

    Ce qui signifie que si vous déclarez cette valeur:

    mayHaveANumber : Maybe Int
    

    Vous pouvez le créer soit par

    mayHaveANumber = Nothing
    

    ou

    mayHaveANumber = Just 5
    

    Les balises Just et Nothing ne servent pas seulement de fonctions constructeurs, elles servent également de destructeurs ou de motifs dans une expression case. Ce qui signifie qu'en utilisant ces modèles, vous pouvez voir à l'intérieur d'un Maybe:

    showValue : Maybe Int -> Html msg
    showValue mayHaveANumber =
        case mayHaveANumber of
            Nothing ->
                Html.text "N/A"
    
            Just number ->
                Html.text (toString number)
    

    Vous pouvez le faire, car le module Maybe est défini comme

    module Maybe exposing 
        ( Maybe(Just,Nothing)
    

    Cela pourrait aussi dire

    module Maybe exposing 
        ( Maybe(..)
    

    Les deux sont équivalents dans ce cas, mais être explicite est considéré comme une vertu dans Elm, surtout lorsque vous écrivez un package.


  1. Masquage des détails d'implémentation
    Comme indiqué ci-dessus, c'est un choix délibéré que les fonctions constructeurs de Maybe soient visibles pour les autres modules.

    Il existe cependant d'autres cas où l'auteur décide de les cacher. n exemple de ceci dans le noyau est Dict . En tant que consommateur du package, vous ne devriez pas pouvoir voir les détails d'implémentation de l'algorithme d'arbre rouge/noir derrière Dict et jouer directement avec les nœuds. Masquer les fonctions du constructeur force le consommateur de votre module/package à créer uniquement des valeurs de votre type (puis à transformer ces valeurs) via les fonctions que vous exposez.

    C'est la raison pour laquelle parfois des trucs comme ça apparaissent dans le code

    type Person =
        Person { name : String, age : Int }
    

    Contrairement à la définition type alias En haut de cet article, cette syntaxe crée un nouveau type "union" avec une seule fonction constructeur, mais cette fonction constructeur peut être masquée des autres modules/packages.

    Si le type est exposé comme ceci:

    module Data exposing (Person)
    

    Seul le code dans le module Data peut créer une valeur Person et seul ce code peut y correspondre.

4
Gabor

La principale différence, selon moi, est de savoir si le vérificateur de type vous crie dessus si vous utilisez un type "synomique".

Créez le fichier suivant, placez-le quelque part et exécutez Elm-reactor, ensuite aller à http://localhost:8000 pour voir la différence:

-- Boilerplate code

module Main exposing (main)

import Html exposing (..)

main =
  Html.beginnerProgram
    {
      model = identity,
      view = view,
      update = identity
    }

-- Our type system

type alias IntRecordAlias = {x : Int}
type IntRecordType =
  IntRecordType {x : Int}

inc : {x : Int} -> {x : Int}
inc r = {r | x = .x r + 1}

view model =
  let
    -- 1. This will work
    r : IntRecordAlias
    r = {x = 1}

    -- 2. However, this won't work
    -- r : IntRecordType
    -- r = IntRecordType {x = 1}
  in
    Html.text <| toString <| inc r

Si vous ne commentez pas 2. et commentaire 1. tu verras:

The argument to function `inc` is causing a mismatch.

34|                              inc r
                                     ^
Function `inc` is expecting the argument to be:

    { x : Int }

But it is:

    IntRecordType
1
EugZol

Un alias est juste un nom plus court pour un autre type, similaire class dans la POO. Exp:

type alias Point =
  { x : Int
  , y : Int
  }

Un type (sans alias) vous permettra de définir votre propre type, afin que vous puissiez définir des types comme Int, String, ... pour votre application. Par exemple, dans le cas commun, il peut utiliser pour la description d'un état d'application:

type AppState = 
  Loading          --loading state
  |Loaded          --load successful
  |Error String    --Loading error

Ainsi, vous pouvez facilement le gérer dans view Elm:

-- VIEW
...
case appState of
    Loading -> showSpinner
    Loaded -> showSuccessData
    Error error -> showError

...

Je pense que vous connaissez la différence entre type et type alias.

Mais pourquoi et comment utiliser type et type alias est important avec l'application Elm, vous pouvez vous référer à article de Josh Clayton

0
hien