레코드는 관련된 정보를 이름표와 함께 묶어 놓은 자료 구조예요. JavaScript나 Java의 객체, 또는 C나 Rust의 구조체와 비슷하지만 몇 가지 중요한 차이점이 있어요.
레코드는 중괄호로 만들고, 각 요소는 쉼표로 구분해요.
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 필드를 가진 레코드를 요구하면 2D 점, 3D 점, 우주선 등 그 필드들을 가진 어떤 레코드와도 동작한다는 뜻이에요.
이런 레코드 타입은 { 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 역사를 깊이 파고들면서 팀과 코치, 그리고 그들의 전적을 하나하나 정리하고 서로 비교하고 있어요.
아직 좋아하는 팀이 없어서, 특정 팀을 응원할지 판단하는 알고리즘도 만들게 됐어요. 여러분만의 응원할 팀을 고르는 알고리즘을 개발하는 데 도움이 될 일곱 개의 과제가 있어요.
다음 두 필드를 이 순서대로 가지는 레코드에 대한 Coach 타입 별칭을 정의해요.
name: 코치의 이름 (타입: String)formerPlayer: 코치가 이전에 선수였는지 여부 (타입: Bool)다음 두 필드를 이 순서대로 가지는 레코드에 대한 Stats 타입 별칭을 정의해요.
wins: 승리 수 (타입: Int)losses: 패배 수 (타입: Int)다음 세 필드를 이 순서대로 가지는 레코드에 대한 Team 타입 별칭을 정의해요.
name: 팀의 이름 (타입: String)coach: 팀의 코치 (타입: Coach)stats: 팀의 전적 (타입: Stats)어떤 서드파티 코드와 연동해야 하는데, 그 코드에는 팀이라는 개념이 있지만 매개변수를 다른 순서로 넘겨 호출해요.
다음 시그니처를 가진 createTeam 함수를 추가해요: String -> Stats -> Coach -> Team.
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 } }
나중에는 팀을 응원할지 말지 결정하는 복잡한 알고리즘을 만들 계획이지만, 첫 시도에서는 승리가 패배보다 많은 팀은 모두 응원하기로 했어요.
이 함수를 다른 상황에서도 재사용하고 싶으니, 타입 시그니처에 확장 가능한 레코드를 사용해서 관련 필드만 있으면 어떤 레코드든 받을 수 있게 해요.
함수를 최대한 간결하게 유지하려면, 패턴 매칭으로 함수 매개변수에서 stats를 꺼내면 좋아요.
팀을 받아서 그 팀을 응원해야 하면 True를, 그렇지 않으면 False를 반환하는 rootForTeam 함수를 구현해요.
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
Exercism에 가입하고 Elm 트랙을 개념 28개연습 문제 110개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.