Строго говоря, если вы хотите различать что-то во время компиляции, вам нужно дать ему разные типы. В вашем примере вы можете определить два типа букв, и тогда тип Letter
будет либо первым, либо вторым.
Это немного громоздко, но, вероятно, это единственный прямой способ достичь того, чего вы хотите:
type Vowel = Vowel of string
type Consonant = Consonant of string
type Letter = Choice<Vowel, Consonant>
let writeVowel (Vowel str) = sprintf "%s is a vowel" str
writeVowel (Vowel "a") // ok
writeVowel (Consonant "a") // doesn't compile
let writeLetter = function
| Choice1Of2(Vowel str) -> sprintf "%s is a vowel" str
| Choice2Of2(Consonant str) -> sprintf "%s is a consonant" str
Тип Choice
- это простое распознаваемое объединение, которое может хранить либо значение первого типа, либо значение второго типа - вы можете определить свой собственный распознаваемый союз, но придумать разумный подход довольно сложно. имена для случаев объединения (из-за вложенности).
Кодовые контракты позволяют вам задавать свойства на основе значений, которые были бы более подходящими в этом случае. Я думаю, что они должны работать с F # (при создании приложения F #), но у меня нет опыта их интеграции с F #.
Для числовых типов вы также можете использовать единицы измерения , которые позволяют добавлять дополнительную информацию к типу (например, число имеет тип float<kilometer>
), но это недоступно для string
. Если бы это было так, вы могли бы определить единицы измерения vowel
и consonant
и написать string<vowel>
и string<consonant>
, но единицы измерения фокусируются в основном на численных приложениях.
Так что, возможно, лучший вариант - в некоторых случаях полагаться на проверки во время выполнения.
[EDIT] Чтобы добавить некоторые детали относительно реализации OCaml - я думаю, что хитрость, которая делает это возможным в OCaml, заключается в том, что он использует структурный подтип, что означает (в переводе на термины F #) может определить дискриминированный союз с некоторыми членами (например, только Vowel
), а затем другой с большим количеством членов (Vowel
и Consonant
).
Когда вы создаете значение Vowel "a"
, его можно использовать в качестве аргумента для функций, принимающих любой из типов, но значение Consonant "a"
можно использовать только с функциями, принимающими второй тип.
Это, к сожалению, не может быть легко добавлено в F #, потому что .NET изначально не поддерживает структурные подтипы (хотя это может быть возможно с использованием некоторых приемов в .NET 4.0, но это должно быть сделано компилятором). Итак, я знаю, понимаю вашу проблему, но не знаю, как ее решить.
Некоторая форма структурного подтипирования может быть выполнена с использованием статических ограничений членов в F #, но, поскольку случаи дискриминационного объединения не являются типами с точки зрения F #, я не думаю, что это пригодно для использования здесь.