记录语法 #

一、记录语法基础 #

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 }

九、总结 #

记录语法要点:

  1. 基本语法data T = T { field :: Type }
  2. 自动访问函数field :: T -> Type
  3. 创建记录T { field = value }
  4. 更新记录record { field = newValue }
  5. 模式匹配T { field = var }T { field }
  6. 派生实例deriving (Show, Eq, Ord)
  7. 局限性:字段名冲突、嵌套更新繁琐

掌握记录语法后,让我们继续学习类型别名。

最后更新:2026-03-27