대입
대입 (Assignment)
프로그래밍을 시작하면 가장 먼저 만나는 게 대입(assignment)이에요. Crystal에서 대입 표현식은 이름이 붙은 식별자(보통 변수)에 값을 넣어줍니다. 대입에 쓰는 연산자는 등호(=)예요.
대입의 대상이 될 수 있는 것들은 이렇습니다.
# Assigns to a local variable
local = 1
# Assigns to an instance variable
@instance = 2
# Assigns to a class variable
@@class = 3
# Assigns to a constant
CONST = 4
# Assigns to a setter method
foo.method = 5
foo[0] = 6
본문
대입 대상으로서의 메서드 (Method as assignment target)
등호(=)로 끝나는 메서드를 setter 메서드라고 해요. 이 메서드는 대입의 대상으로 쓸 수 있어요. 대입 연산자의 의미가 메서드 호출에 대한 일종의 문법 설탕(syntax sugar)으로 적용되는 거죠.
setter 메서드를 호출하려면 명시적인 수신자(receiver)가 필요해요. 수신자 없는 형태 x = y는 언제나 지역 변수 대입으로 해석되며, 결코 메서드 x= 호출이 되지 않아요. 괄호를 붙여도, 지역 변수를 읽을 때처럼 메서드 호출로 강제되지는 않습니다.
아래 예시는 setter 메서드를 일반적인 메서드 표기로 부른 경우와 대입 연산자로 부른 경우를 보여줘요. 두 대입 표현식은 동등합니다.
class Thing
def name=(value); end
end
thing = Thing.new
thing.name=("John")
thing.name = "John"
아래 예시는 인덱스 대입 메서드를 일반적인 메서드 표기로 부른 경우와 인덱스 대입 연산자로 부른 경우예요. 두 대입 표현식은 역시 동등합니다.
class List
def []=(key, value); end
end
list = List.new
list.[]=(2, 3)
list[2] = 3
결합 대입 (Combined assignments)
결합 대입은 대입 연산자와 다른 연산자를 합친 거예요. 상수를 제외한 어떤 대상 타입에도 동작합니다.
= 문자를 담고 있는 문법 설탕이 준비되어 있어요.
local += 1 # same as: local = local + 1
이건 대응하는 대상 local이 변수이거나 각각의 getter·setter 메서드를 통해 대입 가능하다는 걸 전제로 해요.
= 연산자의 문법 설탕은 setter 메서드와 인덱스 대입 메서드에도 쓸 수 있어요. ||와 &&는 키 존재 여부를 확인하는 데 []? 메서드를 사용한다는 점을 기억해 두세요.
person.age += 1 # same as: person.age = person.age + 1
person.name ||= "John" # same as: person.name || (person.name = "John")
person.name &&= "John" # same as: person.name && (person.name = "John")
objects[1] += 2 # same as: objects[1] = objects[1] + 2
objects[1] ||= 2 # same as: objects[1]? || (objects[1] = 2)
objects[1] &&= 2 # same as: objects[1]? && (objects[1] = 2)
연쇄 대입 (Chained assignment)
연쇄 대입(chained assignment)으로 여러 대상에 같은 값을 넣을 수 있어요. 상수를 제외한 어떤 대상 타입에도 동작합니다.
a = b = c = 123
# Now a, b and c have the same value:
a # => 123
b # => 123
c # => 123
다중 대입 (Multiple assignment)
식을 쉼표(,)로 구분하면 여러 변수를 동시에 선언/대입할 수 있어요. 상수를 제외한 어떤 대상 타입에도 동작합니다.
name, age = "Crystal", 1
# The above is the same as this:
temp1 = "Crystal"
temp2 = 1
name = temp1
age = temp2
식을 임시 변수에 먼저 대입하기 때문에, 한 줄로 변수 내용을 서로 교환하는 것도 가능해요.
a = 1
b = 2
a, b = b, a
a # => 2
b # => 1
다중 대입은 =로 끝나는 메서드에도 적용돼요.
person.name, person.age = "John", 32
# Same as:
temp1 = "John"
temp2 = 32
person.name = temp1
person.age = temp2
또한 인덱스 대입([]=)에도 적용됩니다.
objects[1], objects[2] = 3, 4
# Same as:
temp1 = 3
temp2 = 4
objects[1] = temp1
objects[2] = temp2
일대다 대입 (One-to-many assignment)
오른쪽에 식이 하나뿐이라면, 왼쪽의 각 변수에 대해 타입이 다음과 같이 인덱스됩니다.
name, age, source = "Crystal, 123, GitHub".split(", ")
# The above is the same as this:
temp = "Crystal, 123, GitHub".split(", ")
name = temp[0]
age = temp[1]
source = temp[2]
추가로, strict_multi_assign 플래그가 제공되면 요소 수가 대상 수와 일치해야 하고, 오른쪽은 반드시 Indexable이어야 해요.
name, age, source = "Crystal, 123, GitHub".split(", ")
# The above is the same as this:
temp = "Crystal, 123, GitHub".split(", ")
if temp.size != 3 # number of targets
raise IndexError.new("Multiple assignment count mismatch")
end
name = temp[0]
age = temp[1]
source = temp[2]
a, b = {0 => "x", 1 => "y"} # Error: right-hand side of one-to-many assignment must be an Indexable, not Hash(Int32, String)
스플랫 대입 (Splat assignment)
대입의 왼쪽에는 스플랫(splat)을 하나 넣을 수 있어요. 스플랫은 다른 대상에 대입되지 않은 값들을 모아둡니다. 오른쪽에 식이 하나라면 범위 인덱스가 사용돼요.
head, *rest = [1, 2, 3, 4, 5]
# Same as:
temp = [1, 2, 3, 4, 5]
head = temp[0]
rest = temp[1..]
스플랫 뒤에 오는 대상에는 음수 인덱스가 사용됩니다.
*rest, tail1, tail2 = [1, 2, 3, 4, 5]
# Same as:
temp = [1, 2, 3, 4, 5]
rest = temp[..-3]
tail1 = temp[-2]
tail2 = temp[-1]
식에 요소가 충분하지 않고 스플랫이 대상의 중간에 있다면, IndexError가 발생해요.
a, b, *c, d, e, f = [1, 2, 3, 4]
# Same as:
temp = [1, 2, 3, 4]
if temp.size < 5 # number of non-splat assignment targets
raise IndexError.new("Multiple assignment count mismatch")
end
# note that the following assignments would incorrectly not raise if the above check is absent
a = temp[0]
b = temp[1]
c = temp[2..-4]
d = temp[-3]
e = temp[-2]
f = temp[-1]
오른쪽 식은 반드시 Indexable이어야 해요. 크기 검사와 Indexable 검사 둘 다 strict_multi_assign 플래그가 없어도 일어납니다(위 일대다 대입 참고).
값이 여러 개라면 Tuple이 만들어져요.
*a, b, c = 3, 4, 5, 6, 7
# Same as:
temp1 = {3, 4, 5}
temp2 = 6
temp3 = 7
a = temp1
b = temp2
c = temp3
밑줄 (Underscore)
밑줄은 어떤 대입의 왼쪽에도 나타날 수 있어요. 밑줄에 대입해도 아무 효과가 없고, 밑줄을 읽을 수도 없습니다.
_ = 1 # no effect
_ = "123" # no effect
puts _ # Error: can't read from _
다중 대입에서 오른쪽이 돌려주는 값 중 일부가 중요하지 않을 때 유용해요.
before, _, after = "main.cr".partition(".")
# The above is the same as this:
temp = "main.cr".partition(".")
before = temp[0]
_ = temp[1] # this line has no effect
after = temp[2]
*_에 대한 대입은 아예 버려져요. 그래서 다중 대입으로 값의 첫 요소와 마지막 요소를, 가운데 요소를 위한 중간 객체를 만들지 않고 효율적으로 뽑아낼 수 있습니다.
first, *_, last = "127.0.0.1".split(".")
# Same as:
temp = "127.0.0.1".split(".")
if temp.size < 2
raise IndexError.new("Multiple assignment count mismatch")
end
first = temp[0]
last = temp[-1]
더 알아보기 (Learn more)
- Crystal 공식 문서 - Assignment
- Operators — 대입/결합 대입 연산자
- compile_time_flags —
strict_multi_assign플래그