Haskell数据类型中的默认值
用面向对象语言定义类时,它通常为成员变量设置默认值。Haskell中是否有机制在记录类型中执行相同的操作?还有一个后续问题:如果我们从一开始就不知道数据构造函数的所有值,但我们从IO交互中获得它们,那么我们可以使用OOP中的生成器模式之类的东西来构建类型吗Haskell数据类型中的默认值,haskell,types,Haskell,Types,用面向对象语言定义类时,它通常为成员变量设置默认值。Haskell中是否有机制在记录类型中执行相同的操作?还有一个后续问题:如果我们从一开始就不知道数据构造函数的所有值,但我们从IO交互中获得它们,那么我们可以使用OOP中的生成器模式之类的东西来构建类型吗 提前感谢一个常见的习惯用法是定义默认值 data A = A { foo :: Int , bar :: String } defaultA :: A defaultA = A{foo = 0, bar = ""} 然后可以(纯粹地)“更
提前感谢一个常见的习惯用法是定义默认值
data A = A { foo :: Int , bar :: String }
defaultA :: A
defaultA = A{foo = 0, bar = ""}
然后可以(纯粹地)“更新”实际值
doSomething :: Bool -> A
doSomething True = defaultA{foo = 32}
doSomething False = defaultA{bar = "hello!"}
伪代码示例:
data Options = O{ textColor :: Bool, textSize :: Int, ... }
defaultOptions :: Options
defaultOptions = O{...}
doStuff :: Options -> IO ()
doStuff opt = ...
main :: IO ()
main = do
...
-- B&W, but use default text size
doStuff defaultOptions{ color = False }
如果没有合理的默认值,可以将字段值包装在中,也可以包装在中
如果你喜欢冒险,你甚至可以使用一个静态的“中间”选项值,它可能缺少几个字段,而“最终”选项值必须包含所有字段。(不过,我不推荐Haskell初学者使用此功能。)
Haskell中是否有机制在记录类型中执行相同的操作
您可以做的是隐藏构造函数,并提供一个函数作为构造函数
例如,假设我们有一个要更新的列表,以及一个修订号,那么我们可以将其定义为:
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
deriving Show
现在,我们可以定义一个函数,用一个初始列表初始化BuildList
:
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
通过将构造函数隐藏在模块
导出中,我们可以隐藏使用另一个修订版而不是修订版0
初始化构造函数的可能性。因此,模块可能看起来像:
module Foo(RevisionList(), revisionList)
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
类似于OOP中的构建器模式
例如,我们可以使用State
monad来实现这一点。例如:
module Foo(RevisionList(), revisionList,
increvision, RevisionListBuilder, prefixList)
import Control.Monad.State.Lazy
type RevisionListBuilder a = State (RevisionList a)
increvision :: RevisionListBuilder a ()
increvision = do
rl <- get
put (rl { revision = 1 + revision rl})
prefixList :: a -> RevisionListBuilder a ()
prefixList x = do
rl <- get
put (rl { theList = x : theList rl })
increvision
现在,我们可以在修订版2
中“制作”一份修订列表,作为最终列表[1,4,2,5]
:
import Control.Monad.State.Lazy(execState)
some_rev_list :: RevisionList Int
some_rev_list = execState some_building (revisionList [2,5])
所以它看起来大概像:
Foo.hs
:
module Foo(RevisionList(), revisionList,
increvision, RevisionListBuilder, prefixList)
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
deriving Show
type RevisionListBuilder a = State (RevisionList a)
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
increvision :: RevisionListBuilder a ()
increvision = do
rl <- get
put (rl { revision = 1 + revision rl})
prefixList :: a -> RevisionListBuilder a ()
prefixList x = do
rl <- get
put (rl { theList = x : theList rl })
increvision
import Foo
import Control.Monad.State.Lazy(execState)
some_building :: RevisionListBuilder Int ()
some_building = do
prefixList 4
prefixList 1
some_rev_list :: RevisionList Int
some_rev_list = execState some_building (revisionList [2,5])
因此,现在我们构建了一个some\u rev\u列表
,其中“building”为some\u building
:
Foo Bar> some_rev_list
RevisionList {theList = [1,4,2,5], revision = 2}
这里已经有了很好的答案,所以这个答案只是对chi和Willem Van Onsem的好答案的补充
在Java和C#等主流面向对象语言中,并不是默认对象未初始化;相反,默认对象通常使用其类型的默认值进行初始化,而对于引用类型,默认值是空引用
Haskell没有空引用,因此不能用空值初始化记录。对象最直接的转换是记录,其中每个组成元素都是可能。然而,这并不是特别有用,但它强调了在OOP中保护不变量是多么困难
构建器模式根本不能解决这个问题。任何生成器都必须以初始生成器对象开始,并且该对象也必须具有默认值
为了获得更多的细节和大量的例子,我写了一篇文章。本系列文章特别关注测试数据生成器模式,但您应该能够看到它是如何概括为Fluent Builder模式的。还有Default
typeclass,您可以使用DeriveAnyClass
和DeriveGeneric
派生它。顺便说一句,它将导出与defaultA
相同的默认值;具有中性行为的值。此说明适用于中的mempty
。如果这些类型是幺半群,那么A
和Options
的默认值可以是标识值,而它们可以是幺半群。你不能构建一个对象,并在获得更多输入时逐渐修改它,因为你不能修改任何东西。构建器模式没有多大用处:您只需执行所有IO,然后一旦获得所需的值,就可以通过构造函数构建值。
Foo Bar> some_rev_list
RevisionList {theList = [1,4,2,5], revision = 2}