声明作为另一个数据扩展的数据的正确方法是什么

时间:2015-10-25 15:30:11

标签: haskell

我正在为一组“事物”建模。在大多数情况下,所有的东西都具有相同的特征。

data Thing = Thing { chOne :: Int, chTwo :: Int }

除了所有成员共享的基本集之外,还有一小部分事物可被视为具有“扩展”特征集。

chThree :: String

我想拥有可以对这两种事物进行操作的函数(这些函数只关注属性chOnechTwo):

foo :: Thing -> Int

我还想拥有能够处理具有chThree特征的事物的函数。

bar :: ThingLike -> String

我能做到

data ThingBase = Thing { chOne :: Int, chTwo :: Int }
data ThingExt  = Thing { chOne :: Int, chTwo :: Int, chThree :: Int }
fooBase :: ThingBase -> Int
fooExt  :: ThingExt  -> Int
bar     :: ThingExt  -> String

但这很可怕。

我想我可以使用类型类,但所有样板文件都表明这是错误的:

class ThingBaseClass a of
  chOne' :: Int
  chTwo' :: Int

instance ThingBaseClass ThingBase where
  chOne' = chOne
  chTwo' = chTwo

instance ThingBaseClass ThingExt where
  chOne' = chOne
  chTwo' = chTwo

class ThingExtClass a of
  chThree' :: String

instance ThingExtClass ThingExt where
  chThree' = chThree

foo :: ThingBaseClass a => a -> Int
bar :: ThingExtClass  a => a -> String

这样做的正确方法是什么?

5 个答案:

答案 0 :(得分:1)

您可以创建一种数据类型,它是两种不同类型事物的类型联合:

data ThingBase = ThingBase { chBaseOne :: Int, chBaseTwo :: Int }
data ThingExt  = ThingExt { chExtOne :: Int, chExtTwo :: Int, chExtThree :: Int }
data ThingLike = CreatedWithBase ThingBase | 
                 CreatedWithExt ThingExt

然后对于任何应该 一个ThingBaseThingExt的函数,并根据不同的情况做不同的事情,你可以在类型构造函数上进行模式匹配:

foo :: ThingLike -> Int
foo (CreatedWithBase (ThingBase c1 c2)) = c1 + c2
foo (CreatedWithExt (ThingExt c1 c2 c3)) = c3

-- Or another way:
bar :: ThingLike -> Int
bar (CreatedWithBase v) = (chBaseOne v) + (chBaseTwo v)
bar (CreatedWithExt v) = chExtThree v

这具有好处,它迫使您迂腐地指定ThingBaseThingExt在处理ThingLikeCreatedWithBase时所处理的任何地方{1}},通过创建构造函数的额外包装层(我使用的CreatedWithExtThingBase构造函数,其唯一目的是指示您在某个代码点预期的类型)。

但它的缺点是它不允许字段访问器函数的重载名称。我个人并不认为这是一个太大的损失,因为引用属性所需的额外冗长就像一个自然的复杂性惩罚,并有助于激励程序员保持代码稀疏并使用较少的不良访问者/ getter / setter反模式。但是,如果您想要使用重载的访问者名称,那么您应该look into lenses

这只是一个想法,并不是每个问题都适合。您已经使用类型类给出的示例也非常好,我认为没有任何理由将其称为可怕的。

只是唯一的“坏”事情是希望以某种方式隐式地处理ThingExtThingBase不同,而不需要在类型签名中的任何内容或函数体的模式匹配部分告诉人们在两种不同类型区分的时间和地点准确地阅读你的代码,这更像是一种鸭子打字方法,这不是你应该在Haskell中做的事情。

这似乎就是你试图强迫ThingExtThing拥有一个只有<?php include_once('inc/conf/databaseConnect.php'); $query = $sqlLink->query("SELECT * FROM list_users ORDER by id"); while($row = $query->fetch_array()){ echo "<tr>"; echo "<td>".$row['uuid']."</td>"; echo "<td>".$row['firstname'].$row['lastname']."</td>"; echo "<td>".$row['email']."</td>"; echo "<td>".$row['security_key']."</td>"; echo "<td>".$row['phone_no']."</td>"; echo "<td>".$row['activated']."</td>"; echo "<td>".$row['role']."</td>"; echo "</tr>"; } ?> 同名的值构造函数的东西 - 它似乎人为地说,同一个词可以构建任何一种类型的值,但我的感觉是它实际上并不好。我可能会误解。

答案 1 :(得分:1)

这样做的一种方法是等效于OO聚合:

data ThingExt = ThingExt { thing :: Thing, chTree :: Int }

然后您可以在帖子中创建一个类

instance ThingLike ThingExt where
     chOne' = chOne . thing
     chTwo' = chTwo . thing

如果您使用lens库,可以使用makeClassy,这将为您生成所有这些样板。

答案 2 :(得分:1)

一个非常简单的解决方案是引入一个类型参数:

data ThingLike a = ThingLike { chOne, chTwo :: Int, chThree :: a }
  deriving Show 

然后,ThingBase只是ThingLike,没有第三个元素,所以

type ThingBase = ThingLike ()

ThingExt包含额外的Int,所以

type ThingExt = ThingLike Int 

这样做的好处是只使用一个构造函数,只有三个记录访问器。复制很少,编写所需的功能很简单:

foo :: ThingLike a -> Int 
foo (ThingLike x y _) = x+y

bar :: ThingExt -> String 
bar (ThingLike x y z) = show $ x+y+z

答案 3 :(得分:0)

您可以在Maybe ThingExt上使用ThingBase字段,我猜,至少如果您只有一种扩展类型。

如果你有这样的几个扩展,你可以在嵌入数据类型的各种构造函数上使用嵌入和匹配的组合,其中每个构造函数代表一种扩展基础结构的方法。

一旦变得难以管理,类可能变得不可避免,但某种数据类型组合仍然有助于避免重复。

答案 4 :(得分:0)

一个选项是:

data Thing = Thing { chOne :: Int, chTwo :: Int, chThree :: Maybe String }

另一个是

Thing

如果要区分类型级别的两个{{1}} s 并重载访问者,则需要使用类型类。