Haskell:与自己的数据类型混淆。记录语法和唯一字段
我刚刚发现了这一困惑,并希望确认它是什么。当然,除非我错过了什么 比如,我有这些数据声明:Haskell:与自己的数据类型混淆。记录语法和唯一字段,haskell,Haskell,我刚刚发现了这一困惑,并希望确认它是什么。当然,除非我错过了什么 比如,我有这些数据声明: 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"
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区分名称(或任何其他非唯一字段)所属的数据类型并返回正确的结果
- 如果我有几个具有相同字段名的声明,那么正确的处理方法是什么
作为旁注。 通常,我需要返回与上面示例类似的数据类型。 首先,我以元组的形式返回它们(当时我认为这是正确的方法)。但是元组很难使用,因为它不可能像使用“!!”的列表那样简单地提取复杂类型的各个部分。接下来我想到了字典/散列。 当我尝试使用字典时,我想拥有自己的数据类型有什么意义呢? 播放/学习数据类型我遇到了导致我提出上述问题的事实。 因此,使用字典而不是自己的数据类型似乎更容易,因为我可以为不同的对象使用相同的字段
您能详细说明一下这一点并告诉我在现实世界中是如何做到的吗?是的,同一模块中不能有两个字段名相同的记录。字段名作为函数添加到模块的作用域中,因此您可以使用
name vm
而不是vm.name
。您可以在不同的模块中有两个具有相同字段名的记录,并导入其中一个限定为某个名称的模块,但这可能会很难处理
对于这种情况,您可能只需要使用普通代数数据类型:
data VMInfo = VMInfo String String String
(请注意,VMInfo
必须大写。)
现在,您可以通过模式匹配访问VMInfo
的字段:
myFunc (VMInfo name index id) = ... -- name, index and id are bound here
目前,命名字段是顶级函数,因此在一个作用域中只能有一个具有该名称的函数。有计划创建一个新的记录系统,允许在同一范围内的不同记录类型中具有相同名称的字段,但这仍处于设计阶段
目前,您可以使用唯一的字段名,或者在自己的模块中定义每个类型,并使用模块限定名。Haskell记录语法有点老套,但记录名作为函数出现,并且该函数必须具有唯一的类型。因此,您可以在单个数据类型的构造函数之间共享记录字段名称,但不能在不同的数据类型之间共享 如果我有几个具有相同字段名的声明,那么正确的处理方法是什么 你不能。必须使用不同的字段名。如果希望从记录中选择重载名称,可以尝试使用类型类。但基本上,Haskell中的字段名并不像C或Pascal中那样工作。称之为“记录语法”可能是一个错误 但是元组很难使用,因为不可能提取复杂类型的各个部分 实际上,使用模式匹配很容易做到这一点。范例
smallId :: VmInfo -> Bool
smallId (VmInfo { vmId = n }) = n < 10
data VmInfo a = VmInfo { vmId :: Int, vmName :: String, vmInfo :: a }
现在您可以拥有VmInfo字符串
,VmInfo字典
,VmInfo节点
,或者任何您想要的东西
小结:每个字段名必须属于唯一的类型,有经验的Haskell程序员使用静态类型系统,而不是试图绕过它。而且你肯定想学习模式匹配。这不起作用的原因还有很多:小写的类型名和数据构造函数,使用
进行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
...
然后,如果
machine
是VmInfo
以及HostInfo
,name machine
将起作用,镜头可以帮助您减轻获取和设置数据结构元素的痛苦,尤其是当它们嵌套时。如果你眯着眼睛看的话,它们会给你一些东西,有点像面向对象的访问器
在此了解有关镜头系列类型和功能的更多信息:
作为它们外观的示例,这是上面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)
——更新拨杆
updatePaddles::Float->State Pong()
updatePaddles time=do
p(高度/3)$
案例比较区0
燃气轮机->桨叶2+=桨叶运动
LT->桨叶2-=桨叶运动
_->返回()
--确保两个球拍都没有离开比赛场地
桨叶1%=夹持器(桨叶高度/2)
桨叶2%=夹持器(桨叶高度/2)
我建议在原始位置查看整个程序,并查看镜头材料的其余部分;即使你最终没有使用它们,它也非常有趣。谢谢你。遗憾的是Haskell没有数据类型的范围解析操作符。对于普通的ADT,为其编写访问器函数似乎有点乏味