Il semble que le code F # réponde souvent à des types. Certainement
match opt with
| Some val -> Something(val)
| None -> Different()
semble courant.
Mais du point de vue de la POO, cela ressemble énormément à un flux de contrôle basé sur une vérification de type d'exécution, qui serait généralement mal vu. Pour l'expliquer, dans OOP, vous préféreriez probablement utiliser la surcharge:
type T =
abstract member Route : unit -> unit
type Foo() =
interface T with
member this.Route() = printfn "Go left"
type Bar() =
interface T with
member this.Route() = printfn "Go right"
C'est certainement plus de code. OTOH, il me semble que OOP-y a des avantages structurels:
- l'extension à une nouvelle forme de
T
est facile; - Je n'ai pas à m'inquiéter de trouver une duplication du flux de contrôle de choix d'itinéraire; et
- le choix de l'itinéraire est immuable en ce sens qu'une fois que j'ai un
Foo
en main, je n'ai plus à me soucier deBar.Route()
la mise en œuvre
Y a-t-il des avantages à faire correspondre des modèles à des types que je ne vois pas? Est-il considéré comme idiomatique ou s'agit-il d'une capacité qui n'est pas couramment utilisée?
object-oriented
functional-programming
f#
pattern-matching
Larry OBrien
la source
la source
But from an OOP perspective, that looks an awful lot like control-flow based on a runtime type check, which would typically be frowned on.
- semble trop dogmatique. Parfois, vous voulez séparer vos opérations de votre hiérarchie: peut-être 1) vous ne pouvez pas ajouter une opération à une hiérarchie b / c vous ne possédez pas la hiérarchie; 2) les classes dont vous voulez avoir l'op ne correspondent pas à votre hiérarchie; 3) vous pouvez ajouter l'op à votre hiérarchie, mais ne voulez pas b / c vous ne voulez pas encombrer l'API de votre hiérarchie avec un tas de conneries que la plupart des clients n'utilisent pas.Some
et ceNone
ne sont pas des types. Ce sont tous les deux des constructeurs dont les types sontforall a. a -> option a
etforall a. option a
(désolé, je ne sais pas quelle est la syntaxe des annotations de type en F #).Réponses:
Vous avez raison en ce que les hiérarchies de classes OOP sont très étroitement liées aux unions discriminées en F # et que la correspondance de modèle est très étroitement liée aux tests de type dynamique. En fait, c'est en fait ainsi que F # compile les unions discriminées en .NET!
Concernant l'extensibilité, le problème a deux faces:
Cela dit, F # vous avertira lorsque vous manquerez un cas dans la correspondance de modèle, donc ajouter de nouveaux cas d'union n'est pas si mal.
Concernant la recherche de doublons dans le choix de la racine - F # vous donnera un avertissement lorsque vous avez une correspondance en double, par exemple:
Le fait que "le choix de l'itinéraire est immuable" pourrait également poser problème. Par exemple, si vous vouliez partager l'implémentation d'une fonction entre les cas
Foo
etBar
, mais faire autre chose pour leZoo
cas, vous pouvez facilement coder cela en utilisant la correspondance de modèles:En général, FP se concentre davantage sur la conception initiale des types, puis sur l'ajout de fonctions. Il bénéficie donc vraiment du fait que vous pouvez adapter vos types (modèle de domaine) en quelques lignes dans un seul fichier, puis ajouter facilement les fonctions qui opèrent sur le modèle de domaine.
Les deux approches - OO et FP sont assez complémentaires et présentent toutes deux des avantages et des inconvénients. La chose délicate (venant du point de vue OO) est que F # utilise généralement le style FP par défaut. Mais s'il y a vraiment plus besoin d'ajouter de nouvelles sous-classes, vous pouvez toujours utiliser des interfaces. Mais dans la plupart des systèmes, vous devez également ajouter des types et des fonctions, donc le choix n'a pas vraiment d'importance - et l'utilisation d'unions discriminées en F # est plus agréable.
Je recommanderais cette excellente série de blogs pour plus d'informations.
la source
Vous avez correctement observé que la correspondance de modèle (essentiellement une instruction de commutateur suralimenté) et la répartition dynamique ont des similitudes. Ils coexistent également dans certaines langues, avec un résultat très agréable. Cependant, il existe de légères différences.
Je pourrais utiliser le système de type pour définir un type qui ne peut avoir qu'un nombre fixe de sous-types:
Il n'y aura jamais un autre sous-type de
Bool
ouOption
, donc le sous-classement ne semble pas être utile (certains langages comme Scala ont une notion de sous-classement qui peut gérer cela - une classe peut être marquée comme «finale» en dehors de l'unité de compilation actuelle, mais des sous-types peut être défini à l'intérieur de cette unité de compilation).Parce que les sous-types d'un type comme
Option
sont désormais connus de manière statique , le compilateur peut avertir si nous oublions de gérer un cas dans notre correspondance de modèle. Cela signifie qu'une correspondance de modèle ressemble plus à un abattage spécial qui nous oblige à gérer toutes les options.De plus, l'envoi de méthode dynamique (qui est requis pour la POO) implique également une vérification du type d'exécution, mais d'un type différent. Il est donc peu pertinent si nous effectuons ce type de vérification explicitement via une correspondance de modèle ou implicitement via un appel de méthode.
la source
La correspondance de modèle F # est généralement effectuée avec une union discriminée plutôt qu'avec des classes (et n'est donc pas du tout techniquement une vérification de type). Cela permet au compilateur de vous donner un avertissement lorsque vous n'avez pas comptabilisé les cas dans une correspondance de modèle.
Une autre chose à noter est que dans un style fonctionnel, vous organisez les choses par fonctionnalité plutôt que par données, donc les correspondances de modèles vous permettent de rassembler les différentes fonctionnalités en un seul endroit plutôt que dispersées entre les classes. Cela a également l'avantage que vous pouvez voir comment les autres cas sont traités juste à côté de l'endroit où vous devez apporter vos modifications.
L'ajout d'une nouvelle option ressemble alors à:
la source
En partie, vous le voyez plus souvent dans la programmation fonctionnelle car vous utilisez des types pour prendre des décisions plus souvent. Je me rends compte que vous avez probablement choisi des exemples plus ou moins au hasard, mais l'équivalent OOP de votre exemple de correspondance de motifs ressemblerait plus souvent à:
En d'autres termes, il est relativement rare d'utiliser le polymorphisme pour éviter des choses de routine comme les vérifications nulles dans la POO. Tout comme un programmeur OO ne crée pas d' objet nul dans chaque petite situation, un programmeur fonctionnel ne surcharge pas toujours une fonction, surtout lorsque vous savez que votre liste de modèles est garantie d'être exhaustive. Si vous utilisez le système de type dans plus de situations, vous allez le voir utilisé d'une manière à laquelle vous n'êtes pas habitué.
Inversement, la programmation fonctionnelle idiomatique équivalente à votre exemple de POO n'utiliserait probablement pas de correspondance de modèle, mais aurait des fonctions
fooRoute
etbarRoute
qui seraient transmises comme arguments au code appelant. Si quelqu'un utilisait la correspondance de modèles dans cette situation, cela serait généralement considéré comme incorrect, tout comme quelqu'un qui allume des types serait considéré comme incorrect dans la POO.Alors, quand la correspondance de motifs est-elle considérée comme un bon code de programmation fonctionnelle? Lorsque vous faites plus que simplement regarder les types et lorsque vous étendez les exigences, vous n'aurez pas besoin d'ajouter plus de cas. Par exemple,
Some val
ne vérifie pas seulement leopt
typeSome
, il se lie égalementval
au type sous-jacent pour une utilisation sûre de type de l'autre côté du->
. Vous savez que vous n'aurez probablement jamais besoin d'un troisième cas, c'est donc une bonne utilisation.La correspondance de modèles peut ressembler superficiellement à une instruction de commutateur orientée objet, mais il se passe beaucoup plus de choses, en particulier avec des modèles plus longs ou imbriqués. Assurez-vous de prendre tout ce qu'il fait en compte avant de le déclarer équivalent à un code OOP mal conçu. Souvent, il gère succinctement une situation qui ne peut pas être clairement représentée dans une hiérarchie d'héritage.
la source
Some
etNone
ne sont pas types, donc vous n'êtes pas correspondance de motif sur les types. Vous faites correspondre des motifs sur des constructeurs du même type . Ce n'est pas comme demander "instanceof".