我刚刚发现了这种混乱,并想确认它是什么。当然,除非我错过了什么。
说,我有这些数据声明:
data VmInfo = VmInfo {name, index, id :: String} deriving (Show)
data HostInfo = HostInfo {name, index, id :: String} deriving (Show)
vm = VmInfo "vm1" "01" "74653"
host = HostInfo "host1" "02" "98732"
我一直在想,这似乎是如此自然和合乎逻辑:
vmName = vm.name
hostName = host.name
但显然,这不起作用。我明白了。
所以我的问题是。
当我使用记录语法创建数据类型时,是否必须确保所有字段都具有唯一名称?如果是 - 为什么?
是否有一种干净的方式或类似于“范围解析运算符”的内容,如::
或.
等,以便Haskell区分{{1}的数据类型(或任何其他没有唯一字段)属于并返回正确的结果?
如果我有几个具有相同字段名称的声明,处理此问题的正确方法是什么?
通常,我需要返回类似于上面示例的数据类型。 首先我将它们作为元组返回(当时在我看来是正确的方式)。但是元组很难处理,因为不可能像使用“!!”的列表一样简单地提取复杂类型的单个部分。接下来我想到了词典/哈希。 当我尝试使用字典时,我认为拥有自己的数据类型有什么意义呢? 播放/学习数据类型我遇到了导致上述问题的事实。 所以看起来我更容易使用字典而不是自己的数据类型,因为我可以为不同的对象使用相同的字段。
请您详细说明并告诉我它在现实世界中的表现如何?
答案 0 :(得分:17)
Haskell记录语法有点乱,但记录名称出现为函数,并且该函数必须具有唯一类型。因此,您可以在单个数据类型的构造函数之间共享记录字段名称,但不能在不同的数据类型之间共享。
如果我有几个具有相同字段名称的声明,那么处理此问题的正确方法是什么?
你做不到。您必须使用不同的字段名称。如果要从记录中选择重载名称,可以尝试使用类型类。但基本上,Haskell中的字段名称不能像C或Pascal那样工作。将其称为“记录语法”可能是一个错误。
但是元组很难处理,因为不可能提取复杂类型的单个部分
实际上,使用模式匹配可以非常简单。实施例
smallId :: VmInfo -> Bool
smallId (VmInfo { vmId = n }) = n < 10
至于如何在“现实世界”中完成,Haskell程序员往往非常依赖于知道每个字段在编译时的类型。如果希望字段类型发生变化,Haskell程序员会引入类型参数来携带不同的信息。实施例
data VmInfo a = VmInfo { vmId :: Int, vmName :: String, vmInfo :: a }
现在,您可以拥有VmInfo String
,VmInfo Dictionary
,VmInfo Node
或其他任何内容。
摘要:每个字段名称必须属于唯一类型,经验丰富的Haskell程序员使用静态类型系统而不是尝试解决它。你肯定想了解模式匹配。
答案 1 :(得分:10)
为什么这不起作用还有更多原因:小写的类型名称和数据构造函数,使用.
的OO语言式成员访问。在Haskell中,那些成员访问函数实际上是自由函数,即vmName = name vm
而不是vmName = vm.name
,这就是为什么它们在不同的数据类型中不能具有相同的名称。
如果您真的想要可以对VmInfo
和HostInfo
对象进行操作的函数,则需要一个类型类,例如
class MachineInfo m where
name :: m -> String
index :: m -> String -- why String anyway? Shouldn't this be an Int?
id :: m -> String
并制作实例
instance MachineInfo VmInfo where
name (VmInfo vmName _ _) = vmName
index (VmInfo _ vmIndex _) = vmIndex
...
instance MachineInfo HostInfo where
...
如果name machine
是machine
以及VmInfo
,则HostInfo
将有效。
答案 2 :(得分:5)
目前,命名字段是顶级函数,因此在一个范围内只能有一个具有该名称的函数。有计划创建一个新的记录系统,允许在同一范围内的不同记录类型中具有相同名称的字段,但仍处于设计阶段。
目前,您可以使用唯一的字段名称,或在其自己的模块中定义每种类型并使用模块限定名称。
答案 3 :(得分:5)
镜头可以帮助您解决获取和设置数据结构元素时遇到的一些麻烦,特别是当它们嵌套时。如果你眯着眼睛,它们会给你一些外观,就像面向对象的访问器一样。
在此处详细了解Lens系列类型和功能:http://lens.github.io/tutorial.html
作为它们外观的示例,这是来自上述github页面中的Pong示例的片段:
data Pong = Pong
{ _ballPos :: Point
, _ballSpeed :: Vector
, _paddle1 :: Float
, _paddle2 :: Float
, _score :: (Int, Int)
, _vectors :: [Vector]
-- Since gloss doesn't cover this, we store the set of pressed keys
, _keys :: Set Key
}
-- Some nice lenses to go with it
makeLenses ''Pong
这使得镜头可以通过一些TemplateHaskell魔术来访问没有下划线的成员。
稍后,有一个使用它们的例子:
-- Update the paddles
updatePaddles :: Float -> State Pong ()
updatePaddles time = do
p <- get
let paddleMovement = time * paddleSpeed
keyPressed key = p^.keys.contains (SpecialKey key)
-- Update the player's paddle based on keys
when (keyPressed KeyUp) $ paddle1 += paddleMovement
when (keyPressed KeyDown) $ paddle1 -= paddleMovement
-- Calculate the optimal position
let optimal = hitPos (p^.ballPos) (p^.ballSpeed)
acc = accuracy p
target = optimal * acc + (p^.ballPos._y) * (1 - acc)
dist = target - p^.paddle2
-- Move the CPU's paddle towards this optimal position as needed
when (abs dist > paddleHeight/3) $
case compare dist 0 of
GT -> paddle2 += paddleMovement
LT -> paddle2 -= paddleMovement
_ -> return ()
-- Make sure both paddles don't leave the playing area
paddle1 %= clamp (paddleHeight/2)
paddle2 %= clamp (paddleHeight/2)
我建议在原始位置检查整个程序,并查看剩余的镜头材料;即使你最终没有使用它们也很有趣。
答案 4 :(得分:4)
是的,您在同一模块中不能有两个具有相同字段名称的记录。字段名称作为函数添加到模块的范围中,因此您将使用name vm
而不是vm.name
。你可以在不同的模块中有两个具有相同字段名称的记录,并导入一个限定为某个名称的模块,但这可能很难处理。
对于这样的情况,您应该只使用普通的代数数据类型:
data VMInfo = VMInfo String String String
(请注意,VMInfo
有大写。)
现在,您可以通过模式匹配访问VMInfo
的字段:
myFunc (VMInfo name index id) = ... -- name, index and id are bound here