J'apprends le F #. J'ai commencé FP avec Haskell, et je suis curieux de savoir cela.
Étant donné que F # est un langage .NET, il semble plus raisonnable pour moi de déclarer l'interface comme Mappable
, tout comme la Functor
classe de type haskell .
Mais comme dans l'image ci-dessus, les fonctions F # sont séparées et implémentées seules. Quel est l'objectif de conception d'une telle conception? Pour moi, introduire Mappable.map
et implémenter cela pour chaque type de données serait plus pratique.
The community is here to help you with specific coding, algorithm, or language problems.
engloberait également les questions de conception de langage, tant que les autres critères sont remplis.map
et avoir une autre fonction d'ordre supérieur commune pour chaque type de collection. Une interface n'aiderait guère car elle nécessite toujours que chaque type de collection fournisse une implémentation distincte.Réponses:
Oui, une question très simple à première vue. Mais si vous prenez le temps d'y réfléchir jusqu'à la fin, vous entrez dans les profondeurs de la théorie des types incommensurable. Et la théorie des types vous regarde également.
Tout d'abord, bien sûr, vous avez déjà correctement compris que F # n'a pas de classes de type, et c'est pourquoi. Mais vous proposez une interface
Mappable
. Ok, regardons ça.Disons que nous pouvons déclarer une telle interface. Pouvez-vous imaginer à quoi ressemblerait sa signature?
Où
f
est le type implémentant l'interface. Oh, attendez! F # n'a pas ça non plus! Voicif
une variable de type de type supérieur, et F # n'a pas du tout de type supérieur. Il n'y a aucun moyen de déclarer une fonctionf : 'm<'a> -> 'm<'b>
ou quelque chose comme ça.Mais ok, disons que nous avons aussi surmonté cet obstacle. Et maintenant , nous avons une interface
Mappable
qui peut être mis en œuvre parList
,Array
,Seq
et l'évier de la cuisine. Mais attendez! Nous avons maintenant une méthode au lieu d'une fonction, et les méthodes ne se composent pas bien! Regardons l'ajout de 42 à chaque élément d'une liste imbriquée:Regardez: maintenant nous devons utiliser une expression lambda! Il n'existe aucun moyen de passer cette
.map
implémentation à une autre fonction en tant que valeur. Effectivement, la fin des "fonctions en tant que valeurs" (et oui, je sais, utiliser un lambda n'a pas l'air très mal dans cet exemple, mais croyez-moi, ça devient très moche)Mais attendez, nous n'avons toujours pas fini. Maintenant qu'il s'agit d'un appel de méthode, l'inférence de type ne fonctionne pas! Étant donné que la signature de type d'une méthode .NET dépend du type de l'objet, le compilateur n'a aucun moyen d'inférer les deux. Il s'agit en fait d'un problème très courant pour les débutants lors de l'interopérabilité avec les bibliothèques .NET. Et le seul remède est de fournir une signature de type:
Oh, mais ce n'est pas encore suffisant! Même si j'ai fourni une signature pour
nestedList
lui-même, je n'ai pas fourni de signature pour le paramètre lambdal
. Quelle devrait être cette signature? Diriez-vous que cela devrait l'êtrefun (l: #Mappable) -> ...
? Oh, et maintenant nous sommes finalement arrivés aux types de rang N, comme vous le voyez,#Mappable
est un raccourci pour "tout type'a
tel que'a :> Mappable
" - c'est-à-dire une expression lambda qui est elle-même générique.Ou, alternativement, nous pourrions revenir à la bienveillance supérieure et déclarer le type de
nestedList
plus précisément:Mais ok, mettons de côté l'inférence de type pour l'instant et revenons à l'expression lambda et à la façon dont nous ne pouvons plus passer en
map
tant que valeur à une autre fonction. Disons que nous étendons un peu la syntaxe pour permettre quelque chose comme ce que fait Elm avec les champs d'enregistrement:Quel serait le type
.map
? Il faudrait que ce soit un type contraint , comme dans Haskell!Wow OK. Mis à part le fait que .NET ne permet même pas à de tels types d'exister, nous venons de récupérer les classes de types!
Mais il y a une raison pour laquelle F # n'a pas de classes de type en premier lieu. De nombreux aspects de cette raison sont décrits ci-dessus, mais une façon plus concise de le dire est: la simplicité .
Pour vous voyez, c'est une pelote de laine. Une fois que vous avez des classes de type, vous devez avoir des contraintes, une plus grande gentillesse, un rang N (ou au moins un rang 2), et avant de le savoir, vous demandez des types imprédicatifs, des fonctions de type, des GADT et tous les reste.
Mais Haskell paie un prix pour tous les cadeaux. Il s'avère qu'il n'y a aucun bon moyen de déduire tout cela. Les types de type supérieur fonctionnent, mais les contraintes ne fonctionnent pas déjà. Rang-N - n'en rêve même pas. Et même lorsque cela fonctionne, vous obtenez des erreurs de type que vous devez avoir un doctorat pour comprendre. Et c'est pourquoi dans Haskell, vous êtes gentiment encouragé à mettre des signatures de type sur tout. Eh bien, pas tout - tout , mais vraiment presque tout. Et là où vous ne mettez pas de signatures de type (par exemple à l'intérieur
let
etwhere
) - surprise-surprise, ces endroits sont en fait monomorphisés, vous êtes donc essentiellement de retour dans le pays F # simpliste.En F #, en revanche, les signatures de type sont rares, principalement pour la documentation ou pour l'interopérabilité .NET. En dehors de ces deux cas, vous pouvez écrire un gros programme complexe en F # et ne pas utiliser une seule fois une signature de type. L'inférence de type fonctionne correctement, car il n'y a rien de trop complexe ou ambigu pour qu'elle puisse être gérée.
Et c'est le gros avantage de F # sur Haskell. Oui, Haskell vous permet d'exprimer des choses super complexes d'une manière très précise, c'est bien. Mais F # vous permet d'être très délirant, presque comme Python ou Ruby, et toujours avoir le compilateur vous attraper si vous trébuchez.
la source