3
votes

F # Enregistrements avec membres vs classes

En F #, vous pouvez définir des enregistrements avec des fonctions membres, et vous pouvez également créer des classes.

type Album(
     Id: int, 
     Name: string, 
     DateReleased: DateTime, 
     Genre: Genre ) as me =
     do
         me.PrintMessage()
     member this.PrintMessage() =
         printf "Hello %s" Name

ainsi utilisé

let first = 
        { Id = 1; 
          Name = "Hello World!"
          Genre = KPop;
          DateReleased = DateTime(1991, 8, 12) }
first.PrintMessage()

et avec classes cela pourrait être quelque chose comme ça.

type Album = {
    Id: int
    Name: string
    DateReleased: DateTime
    Genre: Genre }
with
    member this.PrintMessage() =
        printf "Hello from %s\n" this.Name

Quand est-il pratique d'utiliser l'un ou l'autre? La modélisation de domaine peut-elle être effectuée uniquement avec des enregistrements?

Une classe peut-elle être convertie en enregistrement et vice versa par une diffusion FSharp cachée?

f#

5 commentaires

Vous trouverez des réponses à ces questions et à des questions similaires sur fsharpforfunandprofit.com


Et ici: fsharp.org/specs/component-design-guidelines


Merci @BentTranberg.


Je pense que vous trouverez également certains de ces éléments intéressants, en particulier la diapositive vers 54:15 - youtube.com / watch? v = MGLxyyTF3OM


Vous pouvez utiliser [] sur des enregistrements immuables afin de les utiliser comme des entités avec des bases de données et autres. Ils auront alors également un constructeur par défaut.


3 Réponses :


6
votes

La modélisation de domaine peut être effectuée avec les enregistrements ET les classes. Je pense que le moment d'utiliser l'un ou l'autre est avant tout une question de préférence et d'environnement. Personnellement, je n'utilise généralement pas de cours, mais je connais des personnes qui les utilisent assez fréquemment. Les classes reposent davantage sur la mutation et sont plus performantes au détriment de la facilité d'utilisation. Personnellement, je pense que si vous voulez écrire un style orienté objet, utilisez des classes et si vous écrivez un style fonctionnel, utilisez des enregistrements. Enregistre la correspondance et la déconstruction des modèles plus facilement. Une partie de cette distinction est culturelle, ce n'est pas parce que vous pouvez faire quelque chose d'une manière particulière que d'autres personnes pourront vous comprendre facilement. Le code est écrit pour être lu, à la fois par vous-même et par les autres. Lorsque vous choisissez des enregistrements ou des classes, il communique l'intention sur le style et l'utilisation de ces types. Enfin, je ne sais pas qu'une classe peut être convertie en un enregistrement dans F # et si c'est le cas, je le recommanderais quand même. En effet, l'utilisation d'enregistrements en tant que classes et de classes en tant qu'enregistrements entre en conflit avec les normes culturelles autour de ces constructions. Je pense personnellement que votre exemple de membre convient à la fois aux enregistrements et aux classes. J'aime normalement utiliser des modules avec le même nom pour les fonctions associées à un type donné, car cela peut rendre les choses moins déroutantes lorsque la fonction consomme autre chose que le type d'enregistrement. Cependant, je ne pense pas que quelqu'un de bien élevé rechigne à l'un ou l'autre de vos exemples de code.

type Album(
    Id: int, 
    Name: string, 
    DateReleased: string, 
    Genre: int ) as me =
    let mutable genre = Genre  
    //readonly property
    member val Id = Id
    //autoproperty with get and set
    member val Name = Name with get, set
    member val DateReleased = DateReleased with get, set
    //property with explicit get and set
    member this.Genre 
        with get () = genre
        and set (value) = genre <- value

voir Quand dois-je utiliser let, member val et member this?


5 commentaires

Merci pour votre gentille réponse. Je veux avoir un moyen simple d'initier mes étudiants à la POO, sans la complexité et la verbosité de Java, j'ai donc opté pour F #. Et en même temps, je ne veux pas expliquer SQL, ou ORM, puisque c'est le travail d'un autre enseignant, j'ai donc choisi LiteDB pour la persistance. C'est là que les cours ne me conviennent pas. Je ne sais pas comment enregistrer les classes (seuls les attributs et non les membres) peuvent être enregistrés dans LiteDB. Merci beaucoup pour votre réponse


Tout d'abord, merci beaucoup d'avoir enseigné aux gens F #! Deuxièmement, je pense que puisque vous écrivez la POO, les classes F # sont probablement mieux adaptées à ce que vous faites. Je vais mettre à jour ma réponse pour montrer comment utiliser les propriétés.


Si je comprends bien, cela devrait fonctionner avec LiteDB car le member val Name = Name with get, set est le même que ce que LiteDB attend en C #.


Si la mutabilité (la possibilité de modifier les propriétés) n'est pas importante pour vous, je pense qu'un enregistrement conviendrait probablement.


Merci encore. Comme vous pouvez l'image, la mutabilité est importante dans la POO traditionnelle, donc les classes sont mon choix (bien sûr pas pour la modélisation d'objets simples). Et merci pour l'explication de get, set with members



1
votes

Pour compliquer un peu les choses, il n'y a pas que les champs d'enregistrement F # et les propriétés CLI en tant que conteneurs de données. Les deux sont les mêmes sous le capot: les accesseurs aux champs d'enregistrement F # immuables sont implémentés comme des récupérateurs de propriété CLI.

Si votre cas d'utilisation le permet, vous pouvez également utiliser des champs CLI, avec des définitions F # mutable val . D'autre part, la définition similaire, en lecture seule val est également exposée par un getter de propriété.

type Album =
    // CLI property getter
    val Id : int
    // mutable CLI field
    [<DefaultValue>]
    val mutable Name : string
    // non-primary constructor with sequence construction (before)
    new (id: int, name : string) as me =
         { Id = id } then me.Name <- name

Il y a des inconvénients:

  • absence de constructeur principal,
  • la lourdeur de l'expression de constructeur supplémentaire,
  • assurer l'initialisation par défaut du type de champ, avec DefaultValueAttribute

Et cela peut être considéré comme unidiomatique.


2 commentaires

Cette approche semble encore moins verbeuse et facile. Et j'aime la simplicité


J'étais presque sûr que je devrais utiliser des cours, mais je pense que dans chaque cas, vous devriez étudier et adopter l'approche la plus simple. Et en cela, F # gagne grâce à ses nombreuses possibilités. Il y a beaucoup à évaluer et à prouver. Je vous remercie



0
votes

Vous pouvez également envisager d'avoir un module avec le même nom que le type d'enregistrement comme ceci:

type Album = {
    Id: int
    Name: string
    DateReleased: DateTime
    Genre: Genre
}
module Album =
    let printMessage album =
        printfn "Hello from %s" album.Name

let first = {
    Id = 1
    Name = "Hello World!"
    Genre = KPop
    DateReleased = DateTime(1991, 8, 12)
}
Album.printMessage first


2 commentaires

Largeur de cette approche, puis-je insérer cet enregistrement / module à partir d'un autre enregistrement / module?


Le module Album est essentiellement un conteneur pour toutes les fonctions liées à l'album pour l'enregistrement donné. C'est ainsi que j'utilise personnellement F #.