String F#中字符串的编译时约束,类似于度量单位-可能吗?

String F#中字符串的编译时约束,类似于度量单位-可能吗?,string,f#,units-of-measurement,String,F#,Units Of Measurement,我正在使用F#开发一个Web应用程序。考虑保护用户输入字符串不受SQL、XSS和其他漏洞的影响 总之,我需要一些编译时约束,使我能够区分普通字符串和表示SQL、URL、XSS、XHTML等的字符串 许多语言都有它,例如Ruby的原生字符串插值功能{…} 对于F#,度量单位似乎做得很好,但它们仅适用于数字类型。 有几种解决方案使用运行时计量单位,但我认为这是我的目标的开销 我已经研究了FSharpPowerPack,似乎很有可能找到类似的字符串: [<MeasureAnnotatedAbbr

我正在使用F#开发一个Web应用程序。考虑保护用户输入字符串不受SQL、XSS和其他漏洞的影响

总之,我需要一些编译时约束,使我能够区分普通字符串和表示SQL、URL、XSS、XHTML等的字符串

许多语言都有它,例如Ruby的原生字符串插值功能
{…}

对于F#,度量单位似乎做得很好,但它们仅适用于数字类型。
有几种解决方案使用运行时计量单位,但我认为这是我的目标的开销

我已经研究了FSharpPowerPack,似乎很有可能找到类似的字符串:

[<MeasureAnnotatedAbbreviation>] type string<[<Measure>] 'u> = string
// Similarly to Core.LanguagePrimitives.IntrinsicFunctions.retype
[<NoDynamicInvocation>]
let inline retype (x:'T) : 'U = (# "" x : 'U #)
let StringWithMeasure (s: string) : string<'u> = retype s

[<Measure>] type plain
let fromPlain (s: string<plain>) : string =
    // of course, this one should be implemented properly
    // by invalidating special characters and then assigning a proper UoM
    retype s

// Supposedly populated from user input
let userName:string<plain> = StringWithMeasure "John'); DROP TABLE Users; --"
// the following line does not compile
let sql1 = sprintf "SELECT * FROM Users WHERE name='%s';" userName
// the following line compiles fine
let sql2 = sprintf "SELECT * FROM Users WHERE name='%s';" (fromPlain userName)

[]键入string很难判断您要做什么。您说过“需要一些运行时约束”,但希望通过度量单位来解决这个问题,度量单位严格来说是编译时。我认为简单的解决方案是创建
SafeXXXString
类(其中
XXX
Sql
Xml
,等等)来验证它们的输入

type SafeSqlString(sql) =
  do
    //check `sql` for injection, etc.
    //raise exception if validation fails
  member __.Sql = sql
它为您提供了运行时安全性,而不是编译时安全性。但是它很简单,可以自我记录,并且不需要阅读F#编译器源代码就可以工作


但是,为了回答你的问题,我认为没有任何方法可以用度量单位来实现这一点。就语法方面而言,您可能能够将其封装在monad中,但我认为这会使它更笨重,而不是更少。

从理论上讲,可以使用“单位”对字符串进行各种编译时检查(这个字符串是“受污染”的用户输入,还是经过净化的?这个文件名是相对的还是绝对的?…)

在实践中,我个人认为它不太实用,因为有太多现有的API只使用“string”,所以您必须非常小心并手动将管道数据从这里转换到那里


我确实认为“字符串”是一个巨大的错误源,处理字符串污染/规范化等的类型系统将是减少错误的静态类型的下一个飞跃之一,但我认为这就像是15年的展望。不过,我对那些尝试使用F#UoM的人感兴趣,看看他们是否能从中获益

最简单的解决办法就是做不到

"hello"<unsafe_user_input>
“你好”
将编写一个类型,该类型具有一些数字类型来包装字符串,就像

type mystring<'t>(s:string) =
    let dummyint = 1<'t>
键入mystring

然后对字符串进行编译时检查

您可以使用有区别的联合:

type ValidatedString = ValidatedString of string
type SmellyString = SmellyString of string

let validate (SmellyString s) =
  if (* ... *) then Some(ValidatedString s) else None
您会得到一个编译时检查,添加两个验证字符串不会生成一个验证字符串(度量单位允许)

如果引用类型的额外开销太大,您可以改用structs。

有点晚(我确信在2月23日和11月30日之间有一种时间格式只有一点不同),我相信这些一行代码与您的目标是兼容的:

type string<[<Measure>] 'm> = string * int<'m>

type string<[<Measure>] 'm> = { Value : string }

type string<[<Measure>] 'm>(Value : string) = struct end
类型字符串
类型字符串(值:字符串)=结构结束

请参阅Haskell对该问题的有趣看法。如果您确实尝试实现此功能,我很有兴趣看看@kvb,你的链接似乎已失效。。。让我为自己设置一个工作链接:)但您的问题提到需要运行时约束——因此会产生混淆。然而,我试图解决这两种可能性。也许我不完全理解这一点,但是除了包装类和验证之外,UoM方法还能提供什么呢?类型安全级别似乎是一样的,但后者(目前)更容易实现。丹尼尔:原因与数字类型非常相似。UoM防止意外混合具有不同用途但存储为相同运行时类型的变量。有一个关于“代码气味”问题的完美解释,由Joel编写,我知道他不想混合不同的字符串类型,但您可以通过各种方式(包装器类等)实现。我只是不清楚UoM方法在这方面的优势。UoM可以以包装器无法做到的方式变得通用。而且它们被擦除,因此没有运行时成本。您认为UoM在数字代码方面有优势吗?同样的优势也适用;您可以围绕浮点数编写结构包装,以千克和秒为单位,但UoM更为优越。我想?是吗?我建议使用结构而不是类,以避免(无可否认)包装器的小开销。谢谢你的建议。问题是DU是一个类,您无法避免构建它
int
实际上是运行时的
int
,这完全没有开销。谢谢您的回答,但是第一个方法强制构造
元组,后面的方法实际上是
结构。我试图避免任何运行时开销。