bientôt moi et mon frère-in-armes Joel libérera la version 0.9 de Wing Beats . C'est une DSL interne écrite en F #. Avec cela, vous pouvez générer xhtml. L'une des sources d'inspiration a été le Module XHTML.M de la Cadre Ocsigen. Je ne suis pas habitué à la syntaxe OCAML, mais je comprends XHTML.M en quelque sorte vérifier de manière statique si les attributs et les enfants d'un élément sont de types valides.
Nous n'avons pas été en mesure de vérifier statiquement la même chose dans F #, et Maintenant, je me demande si quelqu'un a une idée de la façon de le faire? P>
Ma première approche naïve était de représenter chaque type d'élément dans XHTML en tant que cas syndical. Mais malheureusement, vous ne pouvez pas restreindre statiquement les cas valides comme des valeurs de paramètres, comme dans XHTML.M. p>
Puis j'ai essayé d'utiliser des interfaces (chaque type d'élément implémente une interface pour chaque parent valide) et de type contraintes de type, mais je n'ai pas réussi à le faire fonctionner sans utiliser de coulée explicite de manière à faire la solution encombrante à utiliser. Et cela ne se sentait pas comme une solution élégante de toute façon. P>
Aujourd'hui, je cherche des contrats de code, mais cela semble être incompatible avec F # Interactive. Quand je frappe Alt + Entrer, il gèle. P>
juste pour rendre ma question plus claire. Voici un exemple artificiel super simple du même problème: P>
type Letter = | Vowel of string | Consonant of string let writeVowel = function | Vowel str -> sprintf "%s is a vowel" str
6 Réponses :
Strictement parler, si vous voulez distinguer quelque chose à la compilée, vous devez lui donner différents types. Dans votre exemple, vous pouvez définir deux types de lettres, puis le type Ceci est un peu lourd, mais c'est probablement la seule façon directe Pour réaliser ce que vous voulez: p> le type code> code> est une simple union discriminée qui peut stocker une valeur du premier type ou une valeur de Le deuxième type - vous pouvez définir votre propre syndicat discriminé, mais il est un peu difficile de proposer des noms raisonnables pour les cas d'union (en raison de la nidification). p> Les contrats de code vous permettent de spécifier des propriétés basées sur les propriétés. sur les valeurs, qui seraient plus appropriées dans ce cas. Je pense qu'ils devraient travailler avec F # (lors de la création d'une application F #), mais je n'ai aucune expérience avec les intégrer avec F #. P> pour les types numériques, vous pouvez également utiliser Unités de mesure , qui vous permettent d'ajouter des informations supplémentaires au type (par exemple qu'un nombre a un nombre Type Donc, peut-être que la meilleure option est de s'appuyer sur les chèques d'exécution dans certains cas. P> Lorsque vous créez une valeur < Code> VOWEL "A" CODE>, il peut être utilisé comme argument pour fonctionner l'un des types, mais une valeur Ce nonFroyez-vous ne peut pas être facilement ajouté à F #, car .NET n'est pas indigène Supporte le sous-typing structurel (bien que cela puisse être possible en utilisant quelques astuces dans .NET 4.0, mais cela devrait être fait par le compilateur). Donc, je sais comprendre votre problème, mais je n'ai aucune bonne idée de la façon de le résoudre. P> Une forme de sous-typing structurel peut être effectuée en utilisant Contraintes de membre statique en F #, mais depuis des cas d'union discriminés ne sont pas des types du point de vue F #, je ne le fais pas pense que c'est utilisable ici. p> p> lettre code> serait le premier ou le second.
Float
chaîne code>. Si c'était le cas, vous pouvez définir des unités de mesure
voyelle code> et
consonne code> et écrire
string
string
voyelle code>) puis un autre avec plus de membres (
voyelle code> et
consonne code>). p>
consonne "A" ne peut être utilisée qu'avec les fonctions qui prennent le deuxième type . p>
Je comprends cela, mais je suis franchement un peu déçu que je ne peux pas ajouter une sorte de contraintes de cas de syndicat. Pour autant que je sache quand j'appuie sur le module XHTML.M, il est possible dans OCAML. Est-ce que quelqu'un sait pourquoi ce n'est pas possible dans F #? Y a-t-il des limitations dans .net? Au fait, j'aime votre livre "Programmation fonctionnelle réelle"!
@Johan: Merci! Je pense que je comprends votre motivation maintenant - j'ai ajouté des détails concernant OCAML (bien que je ne sois pas un expert). Malheureusement, la fonctionnalité de clé ne peut pas être facilement prise en charge dans F # (AFAIK).
On dirait que la bibliothèque utilise des variantes polymorphes de O'CamL, qui ne sont pas disponibles en F #. Malheureusement, je ne connais pas d'une manière fidèle de les coder dans F #, non plus.
Une possibilité pourrait être d'utiliser des "types fantômes", bien que je soupçonne que cela pourrait devenir difficile à manifester, étant donné le nombre de catégories différentes de contenu que vous avez 'Re trait avec. Voici comment vous pourriez gérer votre voyelle: P>
module Letters = begin (* type level versions of true and false *) type ok = class end type nok = class end type letter<'isVowel,'isConsonant> = private Letter of char let vowel v : letter<ok,nok> = Letter v let consonant c : letter<nok,ok> = Letter c let y : letter<ok,ok> = Letter 'y' let writeVowel (Letter(l):letter<ok,_>) = sprintf "%c is a vowel" l let writeConsonant (Letter(l):letter<_,ok>) = sprintf "%c is a consonant" l end open Letters let a = vowel 'a' let b = consonant 'b' let y = y writeVowel a //writeVowel b writeVowel y
Je viens de comprendre cela aussi - je pensais à utiliser des contraintes de membres statiques (par exemple, ^ t code>), qui sont quelque peu similaires, mais je ne sais pas si cela conduit n'importe où ... (probablement non, mais Peut-être que vous aurez une idée intelligente :-)).
+1 C'est une bonne truc :-) Cela pourrait être encore meilleur si F # a appuyé la covariance. Vous pouvez avoir une hiérarchie de types fantômes et lettre
VOVEL code> Soyez le sous-type de
n'importe quel code>)!
@Tomas - En effet, sans covariance, cette solution a la limitation que vous ne pouvez pas créer facilement une liste contenant a code> et
y code> ou
b code> et < Code> y code>, qui est une honte.
Petite astuce intelligente =) Je l'ajouterai à ma boîte à outils. Ce pourrait être trop lourd, mais qui sait! Nous générons beaucoup de notre code, et si nous pouvons faire ce travail derrière la scène ... Hmmm, vraiment intéressant.
Mon humble suggestion est la suivante: si le système de type ne prend pas facilement en charge de manière statique à la vérification statique de «x», ne passez pas à travers des contorsions ridicules essayant de vérifier statiquement «x». Il suffit de jeter une exception au moment de l'exécution. Le ciel ne tombera pas, le monde ne finira pas. p>
Les contorsions ridicules pour obtenir une vérification statique se présentent souvent au détriment de la complication d'une API et de faire des messages d'erreur indéchiffrables et provoquent d'autres dégradations aux coutures. P>
Eh bien, je suppose que tu as raison. J'ai même entendu dire qu'il y a des gens utilisant des langues où elles ne font presque pas de vérification de type statique. =) Dans notre cas, nous vérifierons les choses statiquement si cela rendra notre DSL plus facile à utiliser, sinon pas. Si nous ne pouvons pas comprendre un moyen de le vérifier statiquement, le validateur de W3C fera le tour au moment de l'exécution!
Je ne suis pas nécessairement en désaccord, mais je pense que cela dépend de l'API. Parfois, vous devez sauter à travers des cerceaux supplémentaires en tant qu'écrivain de la bibliothèque, mais le client obtient une API qui empêche statiquement une utilisation illégale. Pour moi, le coût des messages d'erreur médiocres est souvent compensé par l'avantage de l'exactitude statique (et ce n'est pas comme si F # est connu pour ses messages d'erreur lucides de toute façon :)).
classes?
type Letter (c) = member this.Character = c override this.ToString () = sprintf "letter '%c'" c type Vowel (c) = inherit Letter (c) type Consonant (c) = inherit Letter (c) let printLetter (letter : Letter) = printfn "The letter is %c" letter.Character let printVowel (vowel : Vowel) = printfn "The vowel is %c" vowel.Character let _ = let a = Vowel('a') let b = Consonant('b') let x = Letter('x') printLetter a printLetter b printLetter x printVowel a // printVowel b // Won't compile let l : Letter list = [a; b; x] printfn "The list is %A" l
Bien que cela fonctionne pour l'exemple simplifié donné dans la question, cela ne fonctionnerait pas pour le problème plus général de la bibliothèque XHTML. Comme il existe de nombreuses relations entre types de contenu et types d'entités XHTML, je pense que l'absence d'héritage multiple poserait un problème insurmontable. Il est possible qu'une solution à base d'interface puisse se contenter de cela, mais la question initiale semble demander d'autres approches.
J'ai effectivement essayé une solution basée sur une interface. J'ai fait une preuve de concept et cela a fonctionné. J'ai également réussi à le cacher de l'utilisateur. Ce n'était pas élégant et il y avait beaucoup d'interfaces. Mais cela a fonctionné. Mais ... eh bien, je devais aider à l'inférence de type d'une manière qui a rendu la solution à l'encombrante à utiliser.
Merci pour toutes les suggestions! Juste au cas où il inspirera quiconque à proposer une solution au problème: ci-dessous est une simple page HTML écrite dans nos battements d'aile DSL. La portée est un enfant du corps. Ce n'est pas valide HTML. Ce serait bien s'il ne compilait pas. ou existe-t-il d'autres moyens de le vérifier, que nous n'avons pas pensé? Nous sommes pragmatiques! Chaque moyen possible vaut la peine d'enquêter. L'un des objectifs majeurs avec les battements d'aile est de le faire agir comme un système d'experts HTML (x) qui guide le programmeur. Nous voulons être sûrs d'être sûr qu'un programmeur ne produit que (x) HTML non valide (x) s'il choisit de ne pas manquer de connaissances ou d'erreurs négligentes. P> Nous pensons avoir une solution pour vérifier statiquement les attributs. Il ressemble à ceci: p> Il a ses avantages et ses inconvénients, mais cela devrait fonctionner. P> Eh bien, si quelqu'un arrive avec quoi que ce soit, laissez-nous savoir! p> p>
Vous pouvez utiliser des fonctions en ligne avec des paramètres de type résolus statique pour générer différents types selon le contexte.
test ( div "hello" ) Error: The type 'Span' does not support any operators named 'MakeDiv'
Ah, Duck Typing =) C'est une idée qui mérite d'être enquêté! Je vais tester votre idée dès que possible.
Souhaitez-vous envoyer l'exemple des contrats de code qui gèle FSI à FSBUGS (at) Microsoft Dot Com?