记录语法 #
一、记录语法基础 #
1.1 什么是记录语法 #
记录语法是一种为数据类型字段命名的语法:
haskell
-- 普通数据类型
data Person = Person String Int String
-- 记录语法
data Person = Person
{ name :: String
, age :: Int
, email :: String
}
1.2 基本语法 #
haskell
-- 语法
data 类型名 = 构造器名
{ 字段名1 :: 类型1
, 字段名2 :: 类型2
...
}
-- 示例
data Book = Book
{ title :: String
, author :: String
, year :: Int
, price :: Double
}
1.3 创建记录 #
haskell
-- 使用记录语法创建
john :: Person
john = Person
{ name = "John"
, age = 30
, email = "john@example.com"
}
-- 顺序不重要
jane :: Person
jane = Person
{ email = "jane@example.com"
, name = "Jane"
, age = 25
}
-- 也可以使用位置参数
bob :: Person
bob = Person "Bob" 35 "bob@example.com"
二、字段访问 #
2.1 自动生成访问函数 #
haskell
-- 自动生成以下函数
name :: Person -> String
age :: Person -> Int
email :: Person -> String
-- 使用
getName :: Person -> String
getName = name
-- 示例
name john -- "John"
age john -- 30
email john -- "john@example.com"
2.2 模式匹配 #
haskell
-- 模式匹配记录
showPerson :: Person -> String
showPerson Person { name = n, age = a } =
n ++ " is " ++ show a ++ " years old"
-- 简写形式
showPerson' :: Person -> String
showPerson' Person { name, age } =
name ++ " is " ++ show age ++ " years old"
-- 完整匹配
fullPerson :: Person -> String
fullPerson Person { name, age, email } =
name ++ " (" ++ email ++ "), age " ++ show age
2.3 访问嵌套记录 #
haskell
-- 嵌套记录
data Address = Address
{ street :: String
, city :: String
, zip :: String
}
data Employee = Employee
{ empName :: String
, empAddress :: Address
, salary :: Double
}
-- 访问嵌套字段
getCity :: Employee -> String
getCity = city . empAddress
-- 创建嵌套记录
emp :: Employee
emp = Employee
{ empName = "John"
, empAddress = Address
{ street = "123 Main St"
, city = "New York"
, zip = "10001"
}
, salary = 50000
}
三、记录更新 #
3.1 基本更新 #
haskell
-- 更新单个字段
olderJohn :: Person
olderJohn = john { age = 31 }
-- 更新多个字段
updatedJohn :: Person
updatedJohn = john
{ age = 31
, email = "john.doe@example.com"
}
-- 原始记录不变(不可变)
-- john.age 仍然是 30
3.2 函数式更新 #
haskell
-- 更新函数
celebrateBirthday :: Person -> Person
celebrateBirthday p = p { age = age p + 1 }
-- 修改邮箱
changeEmail :: String -> Person -> Person
changeEmail newEmail p = p { email = newEmail }
-- 使用
olderJane = celebrateBirthday jane
newEmailJane = changeEmail "jane.new@example.com" jane
3.3 嵌套更新 #
haskell
-- 更新嵌套记录
moveEmployee :: String -> Employee -> Employee
moveEmployee newCity emp = emp
{ empAddress = (empAddress emp) { city = newCity }
}
-- 使用lens库可以简化(后续介绍)
四、记录与类型类 #
4.1 派生实例 #
haskell
-- 派生类型类
data Person = Person
{ name :: String
, age :: Int
, email :: String
} deriving (Show, Eq, Ord)
-- Show
print john -- Person {name = "John", age = 30, email = "john@example.com"}
-- Eq
john == jane -- False
john == john -- True
-- Ord(按字段顺序比较)
compare john jane
4.2 自定义Show #
haskell
-- 自定义Show实例
instance Show Person where
show (Person n a e) = n ++ " (" ++ show a ++ ", " ++ e ++ ")"
-- 输出更简洁
print john -- John (30, john@example.com)
4.3 其他类型类 #
haskell
-- Generic(用于泛型编程)
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data Person = Person
{ name :: String
, age :: Int
, email :: String
} deriving (Show, Generic)
-- 用于JSON序列化等
五、记录的高级用法 #
5.1 带类型参数的记录 #
haskell
-- 类型参数
data Result a = Result
{ success :: Bool
, value :: a
, message :: String
}
-- 使用
intResult :: Result Int
intResult = Result True 42 "OK"
stringResult :: Result String
stringResult = Result True "hello" "OK"
5.2 多构造器记录 #
haskell
-- 多构造器记录
data Shape
= Circle
{ radius :: Double
}
| Rectangle
{ width :: Double
, height :: Double
}
| Triangle
{ sideA :: Double
, sideB :: Double
, sideC :: Double
}
-- 使用
circle :: Shape
circle = Circle { radius = 5.0 }
rect :: Shape
rect = Rectangle { width = 3.0, height = 4.0 }
5.3 部分字段 #
haskell
-- 不是所有构造器都有所有字段
data User
= Guest
| Registered
{ username :: String
, email :: String
}
-- 访问时要小心
getUsername :: User -> Maybe String
getUsername Guest = Nothing
getUsername (Registered name _) = Just name
六、记录的局限性 #
6.1 字段名冲突 #
haskell
-- 问题:字段名必须唯一
data Person = Person { name :: String }
data Company = Company { name :: String } -- 错误!name重复
-- 解决方法1:加前缀
data Person = Person { personName :: String }
data Company = Company { companyName :: String }
-- 解决方法2:使用DuplicateRecordFields扩展
{-# LANGUAGE DuplicateRecordFields #-}
data Person = Person { name :: String }
data Company = Company { name :: String }
6.2 嵌套更新繁琐 #
haskell
-- 嵌套更新很繁琐
data Config = Config
{ server :: Server
}
data Server = Server
{ port :: Int
, host :: String
}
-- 更新port
updatePort :: Int -> Config -> Config
updatePort newPort cfg = cfg
{ server = (server cfg) { port = newPort }
}
-- 解决:使用lens库
6.3 部分字段访问 #
haskell
-- 多构造器记录访问可能失败
data Result
= Success { value :: Int }
| Failure { error :: String }
-- 访问value时要小心
getValue :: Result -> Maybe Int
getValue (Success v) = Just v
getValue _ = Nothing
-- 直接访问会报错
-- value (Failure "oops") -- 运行时错误!
七、最佳实践 #
7.1 命名约定 #
haskell
-- 好:使用类型名作为前缀
data Person = Person
{ personName :: String
, personAge :: Int
, personEmail :: String
}
-- 好:简短但有意义的名称
data Point = Point
{ x :: Double
, y :: Double
}
-- 避免:过于通用的名称
-- data Person = Person { name :: String, value :: Int }
7.2 使用类型别名 #
haskell
-- 类型别名提高可读性
type Name = String
type Age = Int
type Email = String
data Person = Person
{ name :: Name
, age :: Age
, email :: Email
}
7.3 严格字段 #
haskell
-- 严格字段避免空间泄漏
data Person = Person
{ name :: !String
, age :: !Int
, email :: !String
}
八、实践示例 #
8.1 配置类型 #
haskell
-- 应用配置
data AppConfig = AppConfig
{ appPort :: Int
, appHost :: String
, appDebug :: Bool
, appDbName :: String
} deriving (Show, Eq)
-- 默认配置
defaultConfig :: AppConfig
defaultConfig = AppConfig
{ appPort = 8080
, appHost = "localhost"
, appDebug = False
, appDbName = "app_db"
}
-- 更新配置
setDebug :: AppConfig -> AppConfig
setDebug cfg = cfg { appDebug = True }
setPort :: Int -> AppConfig -> AppConfig
setPort port cfg = cfg { appPort = port }
8.2 HTTP请求 #
haskell
-- HTTP请求
data HttpRequest = HttpRequest
{ method :: String
, path :: String
, headers :: [(String, String)]
, body :: Maybe String
} deriving (Show)
-- 创建请求
getRequest :: String -> HttpRequest
getRequest path = HttpRequest
{ method = "GET"
, path = path
, headers = []
, body = Nothing
}
postRequest :: String -> String -> HttpRequest
postRequest path content = HttpRequest
{ method = "POST"
, path = path
, headers = [("Content-Type", "application/json")]
, body = Just content
}
8.3 用户管理 #
haskell
-- 用户状态
data UserStatus = Active | Inactive | Suspended
deriving (Show, Eq)
-- 用户记录
data User = User
{ userId :: Int
, userName :: String
, userEmail :: String
, userStatus :: UserStatus
, userCreatedAt :: String
} deriving (Show, Eq)
-- 激活用户
activateUser :: User -> User
activateUser u = u { userStatus = Active }
-- 暂停用户
suspendUser :: User -> User
suspendUser u = u { userStatus = Suspended }
九、总结 #
记录语法要点:
- 基本语法:
data T = T { field :: Type } - 自动访问函数:
field :: T -> Type - 创建记录:
T { field = value } - 更新记录:
record { field = newValue } - 模式匹配:
T { field = var }或T { field } - 派生实例:
deriving (Show, Eq, Ord) - 局限性:字段名冲突、嵌套更新繁琐
掌握记录语法后,让我们继续学习类型别名。
最后更新:2026-03-27