Aar*_*ach 10 f# pattern-matching discriminated-union
我有一个F#Discriminated Union,我想在构造union case的过程中使用一些"构造函数逻辑".让我们说联盟看起来像这样:
type ValidValue =
| ValidInt of int
| ValidString of string
// other cases, etc.
Run Code Online (Sandbox Code Playgroud)
现在,我想对实际传入的值应用一些逻辑,以确保它们有效.为了确保我不会最终处理ValidValue那些不是真正有效的实例(尚未使用验证逻辑构造),我将构造函数设为私有,并公开一个强制构造它们的逻辑的公共函数.
type ValidValue =
private
| ValidInt of int
| ValidString of string
module ValidValue =
let createInt value =
if value > 0 // Here's some validation logic
then Ok <| ValidInt value
else Error "Integer values must be positive"
let createString value =
if value |> String.length > 0 // More validation logic
then Ok <| ValidString value
else Error "String values must not be empty"
Run Code Online (Sandbox Code Playgroud)
这有效,允许我强制执行验证逻辑并确保每个实例ValidValue都有效.然而,问题是该模块之外的任何人都不能进行模式匹配ValidValue以检查结果,从而限制了被歧视的联盟的有用性.
我想允许外部用户仍然模式匹配并与ValidValue其他任何DU一样工作,但如果它有私有构造函数则不可能.我能想到的唯一解决方案是将DU中的每个值包装在一个带有私有构造函数的单例联合类型中,并将实际的ValidValue构造函数保留为public.这会将案例暴露给外部,允许它们匹配,但仍然主要是防止外部调用者构造它们,因为实例化每个案例所需的值将具有私有构造函数:
type VInt = private VInt of int
type VString = private VString of string
type ValidValue =
| ValidInt of VInt
| ValidString of VString
module ValidValue =
let createInt value =
if value > 0 // Here's some validation logic
then Ok <| ValidInt (VInt value)
else Error "Integer values must be positive"
let createString value =
if value |> String.length > 0 // More validation logic
then Ok <| ValidString (VString value)
else Error "String values must not be empty"
Run Code Online (Sandbox Code Playgroud)
现在调用者可以匹配案例ValidValue,但是他们无法读取union案例中的实际整数和字符串值,因为它们包含在具有私有构造函数的类型中.这可以通过value每种类型的函数来修复:
module VInt =
let value (VInt i) = i
module VString =
let value (VString s) = s
Run Code Online (Sandbox Code Playgroud)
不幸的是,现在调用者的负担增加了:
// Example Caller
let result = ValidValue.createInt 3
match result with
| Ok validValue ->
match validValue with
| ValidInt vi ->
let i = vi |> VInt.value // Caller always needs this extra line
printfn "Int: %d" i
| ValidString vs ->
let s = vs |> VString.value // Can't use the value directly
printfn "String: %s" s
| Error error ->
printfn "Invalid: %s" error
Run Code Online (Sandbox Code Playgroud)
有没有更好的方法来强制执行我想要的构造函数逻辑,而不会增加其他地方的负担?
您可以拥有私有案例构造函数,但公开具有相同名称的公共活动模式.以下是定义和使用它们的方法(为简洁起见省略了创建函数):
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 ?
Run Code Online (Sandbox Code Playgroud)