答案 0 :(得分:18)
这可以涵盖真正各种不同的技术。最简单的基本上是不可避免的:如果你想要一个可以为null的值,这可能取决于可变状态或用户输入,你必须用类型系统标记它。这是Maybe
,ST
和IO
分别执行的操作。因此,如果您有上述三种类型之一的内容,您知道它必须是一个不能为空的引用透明值。
上述技巧对语言非常重要,基本上是不可避免的。但是,还有其他方法可以使用类型系统来提高安全性和正确性,这些方法更有趣。
一个有用的例子是阻止SQL注入。 SQL注入是Web应用程序中的常见问题 - 对于基本概念,请查看this XKCD cartoon。我们实际上可以使用类型系统来确保传递给数据库的任何字符串都已经过清理。基本思想是为“原始”字符串创建一个新类型:
newtype Raw a = Raw a
然后,确保从用户获取输入的所有函数都返回Raw
值而不是普通字符串。最后,您只需要一个清理功能:
sanitize :: Raw String -> String
由于普通函数接受String
而不是Raw
,因此您将无法意外地传入未经过类型化的字符串。因为我们使用Raw
定义了newtype
,所以它根本没有运行时开销。
Yesod,一个主要的Haskell Web框架,使用类似这样的技术来防止SQL注入。它还有一些其他很酷的方法,比如使用类型系统来防止数据库中的链接断开;你应该看看它。
在最极端的情况下,您甚至可以使用类型系统来确保矩阵的大小合适。这是一个非常简单的方法。首先,我们需要类型级别的数字:
data Z
data S n
(我们在Peano Arithmetic处使用type level。)这个想法很简单:Z
为0,S
是后继函数,所以{{1 }是1,S Z
是2,依此类推。
我们现在可以编写一个安全矩阵乘法函数:
S (S Z)
此函数仅允许您在内部维度匹配时将矩阵相乘,并确保生成的矩阵在其类型中具有正确的维度。
答案 1 :(得分:7)
嗯,在我看来,像你所看到的那些博览会经常无法传达的事情之一是,这种正确的类型通常是不会“自然地”发生的东西,而是来自使用技术来设计您的类型,这些技术对于来自其他语言的程序员来说并不明显。我最喜欢的一个例子来自the Haskell Wiki page on phantom types;如果您查看该页面上的第1部分,他们就会有这个示例(IMO应该是newtype
声明,而不是data
):
data FormData a = FormData String
a
在做什么?好吧,它的作用是人为地使FormData "foo" :: FormData Validated
和FormData "foo" :: FormData Unvalidated
,尽管它们“真的”相同,现在有不兼容的类型,因此你可以强制你的代码不要混合一个和另一个。好吧,让我不再重复页面所说的内容,它相对容易阅读(至少第1节)。
我在其中一个开关项目中使用的一个更复杂的例子:OLAP超立方体可以看作是一种不是由整数索引索引而是由数据模型对象索引的数组喜欢人,日,产品线等:
-- | The type of Hypercubes.
data Hypercube point value = ...
-- | Access a data point in a hypercube.
get :: Eq point => Hypercube point value -> point -> value
-- | This is totally pseudocode...
data Salesperson = Mary | Joe | Irma deriving Eq
data Month = January | February | ... | December deriving Eq
data ProductLine = Widget | Gagdet | Thingamabob
-- Pseudo-example: compute sales numbers grouped by Salesperson, Month and
-- ProductLine for the combinations specified as the "frame"
salesResult :: HyperCube (Salesperson, Month, ProductLine) Dollars
salesResult = execute salesQuery frame
where frame = [Joe, Mary] `by` [March, April] `by` [Widgets, Gadgets]
salesQuery = ...
-- Read from salesResult how much Mary sold in Widgets on April.
example :: Dollars
example = get salesResult (Mary, April, Widgets)
我希望这比我担心的更有意义。无论如何,这个例子的问题是以下问题:get
的类型,如此处所示,允许您让Hypercube
告诉您它没有的点的值: / p>
badExample :: Dollar
badExample = get salesResult (Irma, January, Thingamabob)
一种可能的解决方案是使get
操作返回Maybe value
而不仅仅value
。但我们实际上可以做得更好;我们可以设计一个API,其中Hypercube
只能询问它包含的值。密钥类似于FormData
示例,但是更复杂的变体。首先我们介绍这种幻像类型:
data Cell tag point = Cell { getPoint :: point } deriving Eq
现在我们重新制定Hypercube
和get
以对标签敏感。我实际上要在这个重新制定的例子中使它更具体。我们从这开始:
{-# LANGUAGE ExistentialTypes #-}
data AuxCube tag point value =
AuxCube { getFrame :: [Cell tag point]
, get :: Cell tag point -> value }
-- This is using a type system extension called ExistentialTypes:
data Hypercube point value = forall tag. Hypercube (AuxCube tag point value)
-- How to use one of these cubes. Suppose we have:
salesResult :: Hypercube (Salesperson, Month, ProductLine) Dollars
salesResult = execute salesQuery points
where points = [Joe, Mary] `by` [March, April] `by` [Widgets, Gadgets]
salesQuery = ...
-- Now to read values, we have to do something like this:
example = case salesResult of
Hypercube (AuxCube frame getter) -> getter (head frame)
如果使用ExistentialTypes
让您感到困惑,我很抱歉,但为了简短说明,本例中的内容基本上是每个Hypercube
包含一个AuxCube
唯一的匿名标记类型参数,因此现在没有两个Hypercube
可以具有相同类型的Cell
。因此,如果我们使用模块系统来阻止调用者构建Cell
,则调用者不可能向Hypercube
询问Cell
它没有值。
致谢:I learned this technique by asking here in Stack Overflow。