Haskell数据类型中的默认值

时间:2017-11-16 21:22:17

标签: haskell

当您使用面向对象语言定义类时,它通常会设置成员变量的默认值。 Haskell中是否有任何机制可以在记录类型中执行相同的操作?还有一个后续问题:如果我们从一开始就不知道数据构造函数的所有值,但我们从IO交互中获取它们,我们可以使用OOP中的构建器模式构建类型吗?

提前致谢

3 个答案:

答案 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模式。