我刚刚发现了这种混乱,并想确认它是什么.当然,除非我错过了一些东西.
说,我有这些数据声明:
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"
Run Code Online (Sandbox Code Playgroud)
我一直以来的想法以及看起来如此自然和合乎逻辑的是:
vmName = vm.name
hostName = host.name
Run Code Online (Sandbox Code Playgroud)
但显然这不起作用.我懂了.
所以我的问题是.
当我使用记录语法创建数据类型时,是否必须确保所有字段都具有唯一名称?如果是 - 为什么?
是否有一种干净的方式或类似于"范围解析运算符"的东西,如::或.等,以便Haskell区分name(或任何其他无唯一字段)属于哪种数据类型并返回正确的结果?
如果我有几个具有相同字段名称的声明,那么处理这个问题的正确方法是什么?
通常,我需要返回类似于上面示例的数据类型.首先我将它们作为元组返回(当时在我看来是正确的方式).但是元组很难处理,因为不可能像使用"!!"的列表一样简单地提取复杂类型的单个部分.接下来我想到了词典/哈希.当我尝试使用字典时,我认为拥有自己的数据类型有什么意义呢?播放/学习数据类型我遇到了导致上述问题的事实.所以看起来我更容易使用字典而不是自己的数据类型,因为我可以为不同的对象使用相同的字段.
能否详细说明一下,告诉我它在现实世界中的表现如何?
Nor*_*sey 18
Haskell记录语法有点像黑客,但记录名称作为一个函数出现,并且该函数必须具有唯一类型.因此,您可以在单个数据类型的构造函数之间共享记录字段名称,但不能在不同的数据类型之间共享.
如果我有几个具有相同字段名称的声明,那么处理这个问题的正确方法是什么?
你不能.您必须使用不同的字段名称.如果要从记录中选择重载名称,可以尝试使用类型类.但基本上,Haskell中的字段名称不能像C或Pascal那样工作.将其称为"记录语法"可能是一个错误.
但是元组很难处理,因为不可能提取复杂类型的单个部分
实际上,使用模式匹配可以很容易.例
smallId :: VmInfo -> Bool
smallId (VmInfo { vmId = n }) = n < 10
Run Code Online (Sandbox Code Playgroud)
至于如何在"现实世界"中完成,Haskell程序员往往非常依赖于知道每个字段在编译时的类型.如果您希望字段的类型发生变化,Haskell程序员会引入一个类型参数来携带不同的信息.例
data VmInfo a = VmInfo { vmId :: Int, vmName :: String, vmInfo :: a }
Run Code Online (Sandbox Code Playgroud)
现在你可以有VmInfo String,VmInfo Dictionary,VmInfo Node,或任何你想要的.
总结:每个字段名称必须属于唯一类型,经验丰富的Haskell程序员使用静态类型系统而不是尝试解决它.你肯定想了解模式匹配.
lef*_*out 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
Run Code Online (Sandbox Code Playgroud)
并制作实例
instance MachineInfo VmInfo where
name (VmInfo vmName _ _) = vmName
index (VmInfo _ vmIndex _) = vmIndex
...
instance MachineInfo HostInfo where
...
Run Code Online (Sandbox Code Playgroud)
然后name machine将工作,如果machine是一个VmInfo以及如果它是一个HostInfo.
目前,命名字段是顶级函数,因此在一个范围内只能有一个具有该名称的函数.有计划创建一个新的记录系统,允许在同一范围内的不同记录类型中具有相同名称的字段,但这仍然处于设计阶段.
目前,您可以使用唯一的字段名称,或在其自己的模块中定义每个类型并使用模块限定的名称.
镜头可以帮助您解决获取和设置数据结构元素时遇到的一些麻烦,特别是当它们嵌套时.如果你眯着眼睛,它们会给你一些外观,就像面向对象的访问器.
在此处了解有关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
Run Code Online (Sandbox Code Playgroud)
这使得镜头可以通过一些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)
Run Code Online (Sandbox Code Playgroud)
我建议在原来的位置检查整个程序,并查看剩下的镜头材料; 即使你最终没有使用它们也很有趣.