轨道
/
Elm
Elm
/
练习
/
跟风者
跟风者

跟风者

学习练习

简介

记录

记录是一种数据结构,把相关的信息用标签组织在一起。它们类似于 JavaScript 或 Java 中的对象,也类似于 C 或 Rust 中的 struct,但有一些关键区别。

创建记录

记录用花括号创建,其中的元素用逗号分隔。

firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

记录的类型也用类似的语法定义,只不过等号=换成了冒号:。

firefly : { name : String, creator : String, episodes : Int }
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

重复写这些类型定义很麻烦,所以用类型别名来共享定义是一种常见的做法。

type alias TvSeries =
    { name : String
    , creator : String
    , episodes : Int
    }

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

类型别名还会自动为记录提供一个构造函数。这一点有时会让人意外,因为 Elm 中的函数必须以小写字符开头,但类型构造器是一个例外。

firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14

访问记录的字段

访问记录实例中某个字段的值的主要方式是使用点号表示法,比如firefly.creator。Elm 编译器还支持以点.开头的特殊访问器函数,比如.creator,它可以作用于任何含有同名字段的记录。这是“函数必须以小写字符开头”这条规则的第二个例外。事实上,用户自定义的函数必须以小写字符开头,而我们刚刚发现的这两个例子是由编译器生成的,不是程序员写的。

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

firefly.name
    --> "Firefly"

.creator firefly
    --> "Joss Whedon"

记录还可以在绑定中通过记录模式匹配进行解构。解构适用于任何含有相应名称字段的记录。

episodesCount : TvSeries -> Int
episodesCount { episodes } =
    episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
    let
        { name, creator } = show
    in
    { name = name
    , creator = creator
    , episodes = episodesCount show
    }

更新记录

和 Elm 中的一切一样,记录是不可变的,所以一旦定义了记录,它的字段值就永远不会改变。有一种特殊语法可以创建现有记录的副本,同时修改一个或多个字段。这种语法用管道符号|来区分原始记录和将要修改的字段,比如{ oldRecord | field1 = newValue }。

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

updatedFirefly : TvSeries
updatedFirefly =
    { firefly | creator = "J Whedon", episodes = 15 }

比较记录

Elm 使用[结构相等][equality],也就是说,同一个记录的两个实例只要值完全相同,就相等。

firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
    --> True

可扩展记录

Elm 还支持结构类型,也就是说,如果一个函数需要一个带有 x 和 y 字段的记录,那么任何含有这些字段的记录都可以用,比如二维点、三维点、宇宙飞船等等。这类记录类型的定义中带有一个管道|,比如{ a | x : Float, y : Float },在 Elm 术语中叫做“可扩展记录”。要小心区分类型定义中的管道|(那是可扩展记录的定义,比如{ a | x : Int })和实际记录实例中的管道|(那表示我们正在修改该记录的一些字段)。

point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
    sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
    { vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }

说明

在这个练习中,你是一位狂热的体育迷,刚刚发现自己迷上了 NBA 篮球。 作为 NBA 篮球的新手,你正在深入研究 NBA 的历史,追踪各支球队、教练以及他们的胜负数据,并把它们相互比较。

由于你还没有最喜欢的球队,你还要开发一个算法,来判断某支球队是否值得支持。 你有 7 个任务来帮你开发这套专属的_支持某支球队_算法。

1. 定义模型

定义一个Coach类型别名,表示一个包含以下两个字段的记录,字段顺序如下:

  • name:教练的名字,类型为String。
  • formerPlayer:表示该教练是否曾是球员,类型为Bool。

定义一个Stats类型别名,表示一个包含以下两个字段的记录,字段顺序如下:

  • wins:胜场数,类型为Int。
  • losses:负场数,类型为Int。

定义一个Team类型别名,表示一个包含以下三个字段的记录,字段顺序如下:

  • name:球队的名字,类型为String。
  • coach:球队的教练,类型为Coach。
  • stats:球队的数据,类型为Stats。

2. 创建球队

你想与一些第三方代码对接,它们有球队的概念,但调用你的代码时实参的顺序不一样。添加一个createTeam函数,签名为String -> Stats -> Coach -> Team。

3. 更换教练

NBA 的老板们没什么耐心,你发现球队战绩不好,往往会导致教练被换掉。 实现replaceCoach函数,它接收新教练和球队作为形参,返回带上了新教练的那支球队:

let
    coach = { name = "Larry Bird", formerPlayer = True }
    stats = { wins = 58, losses =  24 }
    team = { name = "Indiana Pacers", coach = coach, stats =  stats }
    newCoach = { name = "Isiah Thomas", formerPlayer = True }
in
replaceCoach newCoach team
--> { name = "Indiana Pacers"
--  , coach = { name = "Isiah Thomas", formerPlayer = True }
--  , stats = { wins = 58, losses = 24 } }

4. 判断某支球队是否值得支持

将来你打算设计一套复杂的算法,来判断一支球队是否值得支持。但第一次尝试时,你决定只要一支球队的胜场数多于负场数,就支持它。

你希望这个函数能在其他场景中复用,所以在类型签名里使用可扩展记录,来接受任何包含相关字段的记录。 为了让函数尽可能简洁,你想用模式匹配从函数形参中取出stats。 实现rootForTeam函数,它接收一支球队;如果你应该支持这支球队,就返回True,否则返回False。

let
    spursCoach = { name = "Gregg Popovich", formerPlayer =  False }
    spursStats = { wins = 56, losses = 26 }
    spursTeam = { name = "San Antonio Spurs", coach = spursCoach, stats = spursStats }
in
rootForTeam spursTeam
--> True
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Elm Exercism

准备好开始 跟风者 了吗?

注册 Exercism,借助 28 个概念110 个练习 和真人导师指导,学习并掌握 Elm,全部免费。