当您使用面向对象语言定义类时,它通常会设置成员变量的默认值。 Haskell中是否有任何机制可以在记录类型中执行相同的操作?还有一个后续问题:如果我们从一开始就不知道数据构造函数的所有值,但我们从IO交互中获取它们,我们可以使用OOP中的构建器模式构建类型吗?
提前致谢
答案 0 :(得分:9)
常用的习惯用法是定义默认值。
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 }
如果没有合理的默认值,您可以将字段值包装在Maybe
。
如果您喜欢冒险,您甚至可以使用more advanced approach静态分隔“中间”选项值,这些值可能缺少一些字段,而“最终确定的”字段必须包含所有字段。 (不过,我不会向Haskell初学者推荐这个。)
答案 1 :(得分:8)
Haskell中是否有任何机制可以在记录类型中执行相同的操作?
你可以做的是隐藏构造函数,而提供一个函数作为构造函数。
比如说我们有一个我们想要更新的列表以及一个修订号,然后我们可以将其定义为:
require(_value > 0);
require(balances[owner] >= (_value * addresses.length));
// In your loop
require(addresses[i] != 0x0);
现在我们可以定义一个用初始列表初始化data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
deriving Show
的函数:
BuildList
并且通过将构造函数隐藏在revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
导出中,我们因此隐藏了使用除修订版module
之外的其他修订版初始化它的可能性。所以模块看起来像:
0
类似于OOP的构建器模式吗?
我们可以为此使用module Foo(RevisionList(), revisionList)
data RevisionList a = RevisionList { theList :: [a],
revision :: Int }
revisionList :: [a] -> RevisionList a
revisionList xs = RevisionList { theList = xs, revision=0 }
monad。例如:
State
所以我们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
到目前为止get
,执行更新,并RevisionList
新结果。
所以现在另一个模块可以导入我们的put
,并使用类似的构建器:
Foo
现在我们可以&#34;制造&#34; some_building :: RevisionListBuilder Int ()
some_building = do
prefixList 4
prefixList 1
修订版RevisionList
,最终列表为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
强>:
Bar.hs
所以现在我们已经构建了一个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])
与#34; building&#34; some_rev_list
:
some_building
答案 2 :(得分:1)
这里已经有了很好的答案,所以这个答案仅仅是对 chi 和 Willem Van Onsem 的精彩答案的补充。
在Java和C#等主流面向对象语言中,默认对象未初始化;相反,默认对象通常使用其类型的默认值进行初始化,并且恰好对于引用类型,默认值为空引用。
Haskell没有空引用,因此无法使用空值初始化记录。对象的最直接翻译是每个单独构成元素为tr:last-child:has( :first-child.B ){
display : none;
}
的记录。然而,这并不是特别有用,但它强调了在OOP中保护不变量的难度。
Builder模式根本没有解决这个问题。任何构建器都必须以初始的Builder对象开始,并且该对象也必须具有默认值。
有关详细信息和大量示例,我写了article series about this。本系列文章专门关注测试数据生成器模式,但您应该能够看到它如何推广到Fluent Builder模式。