트랙
/
Crystal
Crystal
/
학습 과정
/
Proc과 블록
Pr

Proc과 블록 에서 Crystal

1개의 연습 문제

Proc과 블록 소개

블록은 메서드에 전달할 수 있는 코드 조각이에요. 다른 프로그래밍 언어에서는 이를 익명 함수나 람다라고 불러요. 메서드는 블록을 Proc으로 변환할 수 있는데, Proc은 코드 블록을 나타내요.

def my_method(&block)
  1 + yield
end

my_method { 2 }
# => 3

블록과 Proc은 Crystal에서 반복이나 콜백 같은 곳에 아주 흔히 쓰여요. DSL(도메인 특화 언어)을 만드는 데에도 쓰이는데, DSL의 예로는 웹 프레임워크인 Kemal이나 테스트 프레임워크인 Spec이 있어요. Spec은 바로 Crystal 풀이를 테스트할 때 사용하는 것이에요.

블록

블록을 받을 수 있는 메서드를 만들려면 마지막 인자 앞에 앰퍼샌드(&)를 붙여야 해요. 그러면 블록이 Proc으로 변환되어 그 인자에 할당돼요. 그다음 yield를 사용하면 메서드에 전달된 블록이 실행돼요.

def my_method(&block)
  1 + yield
end

블록을 전달하는 방법에는 두 가지가 있는데, do ... end 키워드를 쓰거나 중괄호({})를 쓰는 거예요. 둘의 가장 큰 차이는 우선순위예요. do ... end는 여러 줄 블록을 지원하지만 중괄호({})는 그렇지 않아요.

my_method do
  2
end
# => 3

my_method { 2 }
# => 3

Yield

yield는 메서드에 전달된 블록을 실행하는 키워드예요. 즉, yield를 호출하지 않으면 블록은 절대 실행되지 않아요. yield에는 블록으로 전달될 인자를 줄 수도 있어요. 블록에 전달할 수 있는 인자 수에는 제한이 없어요. 블록 안에서 인자를 사용하려면 |x, y, ...| 문법을 써요. 여기서 x, y, ...는 인자의 이름이에요.

def my_method(&block)
  1 + yield 2
end

# With curly braces:

my_method { |x| x * 3 }
# => 7

# With do ... end:

my_method do |x|
  x * 3
end
# => 7

위 예시에서 블록은 인자 2를 받아 3을 곱한 뒤 6을 반환해요. 그러면 메서드는 그 결과에 1을 더해 7을 반환해요.

yield에 전달하는 인자 수가 블록이 기대하는 인자 수보다 많으면 오류가 발생해요.

def my_method(&block)
  1 + yield
end

my_method { |x| x * 3 }
# Error: too many block parameters (given 1, expected maximum 0)

블록의 타입

블록은 명시적으로 쓸 수도 있고 암시적으로 쓸 수도 있어요. 암시적 블록이 가장 흔하고, 위 예시에서 본 것이 바로 그런 블록이에요. 블록에 서로 다른 타입의 인자를 줄 수도 있는데, 그러면 컴파일러가 타입을 추론해서 유니언 타입을 만들어요.

def my_method(&block)
  yield "a"
  yield 1
end

my_method do |x|
  p typeof(x)
end

# output: (String | Int32)

명시적 블록을 작성할 때는 인자의 타입과 반환 타입을 지정할 수 있어요. 이때 Arg1Type, Arg2Type, ... -> ResultType 문법을 사용해요. 여기서 인자는 쉼표(,)로 구분하고, 결과 타입은 ->로 구분해요.

def my_method(&block : Int32, Int32 -> Int32)
  1 + yield(2, 3)
end

my_method { |x, y| x * y }
# => 7

다른 명시적 타입과 마찬가지로, 컴파일러는 타입이 기대한 대로인지 강제해요. 블록이 메서드에 지정한 타입과 맞지 않으면 오류가 발생해요.

축약 문법

인자를 하나만 받고 그 인자에 대해 메서드를 하나만 호출하는 블록에는 축약 문법이 있어요. |x|를 쓰는 대신 인자 이름 앞에 &를 쓸 수 있어요.

def my_method(&block)
  yield "a"
end

# This is: 

my_method { |x| x.upcase }
# => "A"

# Is the same as:

my_method &.upcase
# => "A"

Proc

Proc은 블록과 아주 밀접하게 연결되어 있어요. 가장 큰 차이는 Proc은 타입이고 블록은 그렇지 않다는 거예요. Proc은 함수 포인터를 나타내며 변수처럼 주고받을 수 있어요.

Proc을 정의하려면 proc 리터럴 ->(args) { ... } 문법을 사용해요. 인자는 쉼표(,)로 구분하고, 블록은 { ... }로 구분해요. Proc을 호출하려면 call 메서드를 사용해서 인자를 전달해요. 매개변수의 타입은 필수예요. 반환 타입은 proc의 본문에서 추론되지만, 명시적으로 지정할 수도 있어요.

my_proc = ->(x : Int32, y : Int32) : Int32 { x * y }
my_proc.call(2, 3)
# => 6

블록 캡처하기

블록 캡처는 블록으로부터 Proc을 만드는 방법이에요. 메서드의 마지막 인자 앞에 & 연산자를 사용하면 돼요. 그러면 블록이 Proc으로 변환되어 그 인자에 할당돼요.

def my_method(&block)
  block.call
end

my_method { 1 + 2 }
# => 3

메서드에 Proc 전달하기

Proc은 블록을 기대하는 메서드에 전달할 수 있어요. 어떤 조건에 따라 서로 다른 블록을 전달하고 싶을 때 유용해요.

def my_method(&block)
  yield 5
end

my_proc = -> (x) { x * 2 }
my_method(&my_proc)
# => 10

변수의 타입 선언하기

변수를 선언할 때 : Type 문법으로 변수의 타입을 선언할 수 있어요. Proc의 경우에는 Proc((ArgType, ...), ResultType) 문법을 사용해요. 마지막 타입은 항상 반환 타입이고, 나머지는 인자 타입이에요. 즉, 타입이 하나뿐이라면 그것이 반환 타입이고 proc은 인자를 받지 않아요.

my_proc : Proc(Int32, Int32, Int32) = ->(x, y) { x * y }
other_proc : Proc(Int32) = -> { 1 }

메서드에서 proc 선언하기

메서드에서 proc을 선언할 수도 있어요.

def my_method(a, b)
  a + b
end

my_proc = ->my_method(Int32, Int32)
my_proc.call(2, 3)
GitHub에서 편집 링크가 새 창이나 탭에서 열려요

Proc과 블록 배우기

연습이 잠겨 있어요

Proc과 블록 개념을 연습하려면 연습 문제 1개를 더 잠금 해제해요