Discriminated Union - Allow Pattern Matching but Restrict Construction
Unless there's a particular reason that a discriminated union is required, given the particular use case you've provided it sounds like you don't actually want a discriminated union at all since an active pattern would be more useful. For example:
let (|ValidInt|ValidString|Invalid|) (value:obj) =
match value with
| :? int as x -> if x > 0 then ValidInt x else Invalid
| :? string as x -> if x.Length > 0 then ValidString x else Invalid
| _ -> Invalid
At that point, callers can match and be assured that the logic has been applied.
match someValue with
| ValidInt x -> // ...
| _ -> // ...
You can have private case constructors but expose public active patterns with the same names. Here's how you would define and use them (creation functions omitted for brevity):
module Helpers =
type ValidValue =
private
| ValidInt of int
| ValidString of string
let (|ValidInt|ValidString|) = function
| ValidValue.ValidInt i -> ValidInt i
| ValidValue.ValidString s -> ValidString s
module Usage =
open Helpers
let validValueToString = function
| ValidInt i -> string i
| ValidString s -> s
// ð Easy to use ✔
// Let's try to make our own ValidInt ð¤
ValidInt -1
// error FS1093: The union cases or fields of the type
// 'ValidValue' are not accessible from this code location
// 𤬠Blocked by the compiler ✔