如何使用类型系统来防止无意义的表达

时间:2013-09-27 16:26:25

标签: f#

是否有可能以这样的方式设计我的类型:

let fieldValues = [nameField, VText "string"; ageField, VInteger 13]

但不是这样:(从某种意义上说它将是一个编译时错误):

let fieldValues = [nameField, VInteger 13; ageField, VText "string"]

type value = 
    | VText of string
    | VInteger of int

type ty = 
    | TText
    | TInteger

type field = { Id: int; Type: ty; Name: string }

let nameField = { Id=1; Type=TText; Name="Name" }
let ageField = { Id=2; Type=TInteger; Name="Age" }

2 个答案:

答案 0 :(得分:2)

列表中的元组属于value * ty类型。为了让编译器注意到你需要连接两个,你需要让编译器知道'你需要独特的,连接的,状态。这可能需要您删除一些通用:

type DataType = 
| TextData of VText * { Id : int; Type : TText; Name : string }
| IntData of VInteger * { Id : int; Type : TInteger; Name : string }

然后,您将创建一个DataType列表,如果您尝试将VInteger混合到TText记录中,编译器会注意到这一点。因为您已经在一个有区别的联合中明确说明了这些组合。 value DI会有点多余:

type DataType = 
| TextData of string * { Id : int; Type : string; Name : string }
| IntData of int * { Id : int; Type : int; Name : string }

编辑:(我在一个在电话上打字的酒吧)你可以用通用的方式清理它:

type DataType<'a> = {
    Content :  'a * { Id : int; Type : 'a; Name : string }

}

type PossibleType = DataType<int> | DataType<string>

这可能不是理想的方法(其他人会更好);但是,我在这里遵循的原则是编译器只能注意到它之间的关系。很明显,这个解决方案对于TypeA -> ValA关系来说只是相对干净,如果你有很多可能的组合,那么它会变得数字丑陋(此时你需要重新设计DI作为所有可能性的树木或者将变体数据重构为单独的记录。)

答案 1 :(得分:0)

不可能完全按照自己的意愿行事。但是,这里有类似的功能:

type TypedField<'a> = { id : int; name : string }

type FieldConverter<'t> =
    abstract Convert : TypedField<'a> * 'a -> 't

// Necessary only because F# type system won't let you implement FieldConverter<unit>
type FieldFunc =
    abstract Use : TypedField<'a> * 'a -> unit 

type Field =
    abstract ApplyConverter : FieldConverter<'t> -> 't
    abstract ApplyFunc : FieldFunc -> unit

let mkField field value = 
    { new Field with 
        member __.ApplyConverter(f) = f.Convert(field, value) 
        member __.ApplyFunc(f) = f.Use(field, value) }

let nameField : TypedField<string> = { id = 1; name = "Name" }
let ageField : TypedField<int> = { id = 2; name = "Age" }

let fields = [mkField nameField "string"; mkField ageField 13]
// won't compile
let fields' = [mkField nameField 13; mkField ageField "string"]

不幸的是,使用这些字段需要相当多的样板:

// print names and values of all fields
fields
|> List.iter (fun f -> f.ApplyFunc { new FieldFunc with member __.Use(field, value) = printfn "%s: %O" field.name value })