루비 2.6 릴리즈 및 주요 문법 변경 사항 소개
루비 2.6 릴리즈, Happy holidays!
루비Ruby는 마츠모토 유키히로 씨(이하 마츠)Yukihiro Matsumoto가 만든 프로그래밍 언어입니다. 올 해로 25주년을 맞이한 루비 언어는 매년 12월 25일(크리스마스)에는 루비의 새로운 버전을 릴리즈하는 전통이 있습니다. 그리고 올 해도 예정대로 크리스마스에 루비 2.6이 릴리즈되었습니다 🎄
- 루비 2.6.0 릴리즈 | 루비 (영문)
이 글에서는 루비 2.6 설치 방법과 주요한 변경사항들을 소개합니다.
루비 2.6 설치 방법
루비 릴리즈 페이지에서는 소스 코드를 압축 파일로 제공하고 있습니다. 이 파일을 다운로드 받아 직접 빌드하면 2.6.0을 바로 사용해볼 수 있습니다.
맥OSmacOS나 리눅스 배포판을 사용하는 경우 ruby-install
을 사용해 루비 소스코드를 좀 더 쉽게 빌드할 수 있습니다.
$ brew install ruby-install
$ ruby-install ruby-2.6.0
ruby-install
을 사용하는 경우 추가적인 실행 경로 지정 작업이 필요할 수 있습니다. 실행 경로를 지정하고 루비의 버전을 확인해봅니다.*
* 저는 하나의 머신에서 다수의 루비 버전을 사용하기 때문에 주로 direnv
를 사용해 루비 버전과 실행 경로를 설정합니다. 이 방법에 대해서는 direnv로 디렉토리(프로젝트) 별 개발환경 구축하기의 루비 파트를 참고해주세요.
$ ruby --version
ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-darwin18]
크리스마스에 배포된 2.6.0 버전을 확인할 수 있습니다.
인터프리터 / 문법 변경 사항
(실험적 기능) JIT 컴파일러 구현
JIT 컴파일러 구현이 추가되었습니다. 실험적 변경사항으로 ruby
명령어 실행 시에 --jit
옵션을 사용해야만 JIT 컴파일이 적용됩니다. --jit-verbose=1
옵션을 사용해서 실행 중에 자세한 내용을 확인할 수 있습니다.
$ ruby --jit awesome_ruby_code.rb
루비 JIT 구현에 대한 보다 자세한 내용은 루비 커미터 다카시 고쿠분Takashi Kokubun 씨의 발표 자료를 참고해주세요.
- 루비콘프RubyConf 2018 - 루비 2.6의 숨겨진 파워 : JIT by Takashi Kokubun, 발표 자료, 발표 영상(영어)
- 루비카이기RubyKaigi 2018 - 루비 2.6의 메서드 JIT 컴파일러, 발표 자료, 발표 영상(일본어)
무한한(endless) 범위 표현 추가
루비에서는 범위 표현 문법으로 ..
과 ...
을 지원하고 있습니다. 기본적으로 범위의 앞뒤에는 범위의 시작과 끝에 해당하는 객체가 지정되어야 합니다. 2.6부터는 범위의 끝을 생략한 무한한 범위 문법이 추가되었습니다. 예를 들어 1..
은 1부터 무한대까지를 의미합니다. 좀 더 정확히는 1..nil
의 신택스 슈가입니다.
이 문법을 구현한 엔도 유스케 씨에 따르면 이 문법은 아래와 같은 경우에 사용하도록 고안되었다고 합니다.
ary[1..] #=> ary[1..-1]과 같음.
(1..).each {|index| ... } # 1에서부터 무한 루프
# each_with_index을 0이 아닌 1부터 시작
ary.zip(1..) {|elem, index| ... }
include?
메서드를 사용해 Float::INFINITY
가 범위에 포함되어있음을 확인할 수 있습니다.
열거자(Enumerator) 객체로 변환해서 직접 객체를 확인하는 것도 가능합니다.
끝이 없는(endless)의 범위의 반대에 해당하는 시작이 없는(beginless) 범위 객체(#14799)는 아직 논의중에 있습니다.
rescue가 없는 else 절을 지원하지 않음
루비에서는 예외처리를 위해 begin .. rescue .. end
구문을 지원하고 있습니다. 그리고 이 사이에서 예외가 발생하지 않을 때 추가적인 처리를 위한 else
절을 사용할 수 있습니다.
이 문법을 사용할 때 rescue
없이 else
절을 사용하는 게 가능했습니다. 좀 더 정확히는 rescue
절을 0개 이상을 지정할 수 있었습니다. 따라서 rescue
가 없는 else
절이 존재할 수 있었습니다. 루비 2.5에서는 다음 코드가 정상적으로 실행 됩니다.
> begin
> puts "hello, world!"
> else
> puts "else"
> end
warning: else without rescue is useless
Hello, world!
else
=> nil
Hello, world!
와 else
가 출력되고 종료됩니다. 하지만 이는 예외 처리를 위한 문법이라는 점을 고려해보면 이상한 코드입니다. 이미 2.5에서도 이 코드를 실행하면 else without rescue is useless
라는 경고를 보여집니다.
루비 2.6에서는 같은 코드를 실행하면 SyntaxError
가 발생합니다.
영어가 아닌 언어의 대문자를 상수의 첫 글자로 사용 가능
루비의 상수는 아스키 문자의 대문자, 즉 영어 대문자 [A-Z]
로 시작하도록 정의되어있습니다. 2.6부턴 영어가 아닌 언어의 대문자를 상수의 첫 글자로 사용할 수 있게 되었습니다. 예를 들어 루비 2.5에서 그리스어 대문자 감마(Γ
)를 클래스 이름으로 사용하고자 하면 SyntaxError
에러가 발생했습니다.
루비 2.6에서 이 구문은 에러를 발생시키지 않습니다.
키워드 인자의 키 객체로 심볼이 아닌 값을 사용할 수 없도록 변경
마츠가 mruby 구현 버그를 발견하면서, 루비 2.6에서는 심볼이 아닌 키를 키워드 변수로 넘길 수 없도록 수정되었습니다. 버그가 발생하는 코드는 다음과 같습니다.
이 코드의 실행 결과는 [[], {"a"=>1, :a=>1}]
가 기대됩니다만, 실제로는 [[{"a"=>1}], {:a=>1}]
가 출력 됩니다. 이 결과는 현재 루비 2.5.3에서 재현됩니다. 이 버그가 재현되지 않도록 2.6부터는 **k
에 값을 넘겨줄 때 키워드 인자에 반드시 심볼 키를 사용해야합니다. 루비 2.6에서 위의 코드를 실행하면 다음과 같이 에러가 발생합니다.
> def m(*a, **k) [a, k] end
> p m("a" => 1, a: 1)
ArgumentError: non-symbol key in keyword arguments: "a"
지역 변수 섀도잉에 대한 경고를 출력하지 않음
루비 2.6 이전에는 지역 변수로 사용되는 변수명을 블럭에서 사용할 경우 경고가 출력되었습니다. 이는 블럭 안의 변수가 지역 변수의 이름을 가리면서 접근할 수 없게 되기 때문입니다. 이 경고는 더 이상 출력되지 않습니다.
범위를 사용한 플립플롭 문법 비추천(deprecated)
이 문법을 사용하는 경우는 많지 않을 것 같습니다만, 루비에서는 범위 객체로 플립플롭을 사용할 수 있었습니다. 범위 객체에서 시작 조건을 만족하는 시점부터 참이되고, 끝 조건을 만족하는 경우 다시 거짓이 됩니다.
이 문법은 이제 비추천 문법이 되었으며, 사용할 경우 경고가 출력됩니다. 조만간 없어질 것으로 보입니다.
클래스 / 메서드 관련 변경 사항
to_h 메서드가 블럭을 받도록 변경
루비에서 객체를 해시로 변경하는 경우 to_h
메서드를 사용합니다. 해시 객체에서 키, 밸류 값을 특정한 함수로 수정하는 경우를 생각해보겠습니다. 루비 2.6 이전에는 해시 객체에서 map
을 사용해서 키 밸류 값을 수정해야만 했습니다. map
을 적용한 결과는 바로 해시로 만들어지지 않고, [키, 밸류]
값을 담은 배열들로 구성된 배열을 반환하게 됩니다.
> {a: 1.1, b: 1.2}.map{|k, v| [k.to_sym, v ** 20] }
=> [[:a, 6.727499949325611], [:b, 38.33759992447472]]
이 결과에 다시 to_h
를 호출하면 해시로 변환됩니다.
> {a: 1.1, b: 1.2}.map{|k, v| [k.to_sym, v ** 20] }.to_h
=> {:a=>6.727499949325611, :b=>38.33759992447472}
루비 2.6부터는 to_h 메서드에서 블럭을 넘겨받아, 해시의 키, 밸류 값에 함수를 적용하는 작업을 수행할 수 있습니다. 따라서 위의 코드는 이제 아래와 같이 작성할 수 있습니다.
등차 수열 구현을 위한 Enumerator::ArithmeticSequence 클래스 추가
등차 수열 구현을 위한 Enumerator::ArithmeticSequence
클래스가 추가되었습니다. 등차수열은 초항과 공차로 정의됩니다. 이제 Numeric#step
메서드를 사용해서 등차 수열을 만들 수 있습니다.
이 수열은 Enumerator::ArithmeticSequence
의 객체이며, 다음과 같이 무한 수열로 다음값을 생성합니다.
최대값을 지정하는 경우 다음과 같이 to
키워드를 사용합니다.
Numeric#step
이외에 Range#step
메서드도 추가되었습니다. 범위 객체를 사용하면 위와 같은 객체를 다음과 같이 표현할 수 있습니다.
step
메서드의 별칭으로 %
연산자를 사용하는 것도 가능합니다.
Kernel#yield_self의 별칭으로 then이 추가
루비 2.5에서 Object#yield_self
가 메서드가 추가되었습니다. 이 메서드는 객체 자신을 yield
하는 간단한 메서드로 다음과 같이 정의할 수 있습니다.
이 메서드를 어디에 쓰냐고 반문할 수도 있습니다만, 모든 코드를 한 줄에 작성하고자 하는 메소드 체이닝을 애정하는 사람들에게는 꽤나 유용한 함수입니다. *
* 사용법이 궁금하신 분들은 루비 2.5의 yield_self // Michał Łomnicki나 yield_self는 당신이 생각하는 것보다 멋진 기능이다를 참고해주세요.
메서드 이름인 yield_self
에 대한 이슈가 있었고, 결과적으로 마츠에 의해 새로운 이름이 then
으로 결정되었습니다. then
은 루비 키워드랑 겹치기도 하고, 프로미즈에서 관습적으로 사용되는 이름이기 때문에 반대 의견도 많았다고 합니다만, 마츠가 몇 년만에 직접 커밋을 해서 추가되었다고 하네요.
Hash#merge가 여러 개의 인자를 받도록 변경
Hash 클래스의 merge
메서드는 하나의 인자만 받을 수 있었습니다. 따라서 세 개의 해시를 합치려면 다음과 같이 merge
메서드를 여러번 호출해야했습니다.
> {a: 1}.merge({b: 2}, {c: 3})
ArgumentError: wrong number of arguments (given 2, expected 1)
> {a: 1}.merge({b: 2}).merge({c: 3})
=> {:a=>1, :b=>2, :c=>3}
루비 2.6에서는 merge 메서드가 1개 이상의 인자를 받을 수 있도록 수정되었습니다.
이 개선은 @liukoki 씨에 의해서 이루어졌습니다. 루비 코드에 기여한 과정은 블로그에 자세히 소개되어 있습니다(일본어).
File#open 메서드에 모드 문자 x가 추가
open
메서드의 모드 문자로 x
가 추가되었습니다. 이 모드는 w
와 함께 사용합니다. 다음 코드는 foobar.txt
가 이미 존재하면 File exists
에러를 발생시킵니다.
String#split 메서드가 블럭을 받을 수 있도록 변경
split
메서드가 블럭을 넘겨받도록 개선되었습니다. split
로 문자열을 분할한 결과를 each
반복자에 넘기는 것과 비슷하게 동작합니다.
단, split
결과에 each
를 호출한 반환값은 문자열 분할 결과를 담은 배열이 됩니다만, split
에 직접 블럭을 넘기면 원래의 문자열이 반환 됩니다.
Enumerable::Chain 클래스와 Enumerable#chain 메서드 추가
Enumerator
객체를 연결하기 위한 Enumerable::Chain
클래스와 Enumerable#chain
메서드가 추가되었습니다. 서로 다른 클래스의 객체라도 클래스에 each
메서드가 정의되어있다면 Enumerator
객체로 연결하는 것이 가능해졌습니다. 아래는 배열과 무한 범위 객체를 연결하는 예제입니다.
Enumerator#+
메서드를 사용해서 열거자 객체들을 연결하는 것도 가능합니다.
Proc 합성 연산자 >>와 <<가 추가
Proc
객체를 합성하는 연산자 >>
와 <<
가 추가되었습니다. 예를 들어 아래와 같이 i
와 t
라는 함수가 있습니다.
이 함수를 i >> t
로 합성하면 -> x { -> y {1.0 + y}[x] ** 10 }
와 같은 함수가 됩니다.
반면에 i << t
와 같이 합성하면 함수 합성 순서가 반대가 됩니다. 따라서 -> x { 1.0 + -> y {y ** 10}[x] }
함수와 같은 함수가 됩니다.
이 문법은 그루비의 문법에서 차용했다고 합니다.
그 외의 변경 사항
이외에도 추가적인 성능 개선과 다양한 변경 사항이 있습니다.
- Refinement 문법 관련 개선
-
TracePoint
확장 -
Bundler
가 표준 라이브러리로 추가 -
Regexp
,String
에서 유니코드 11.0.0, 이모지 11.0.0 지원. -
RubyVM::AbstractSyntaxTree
클래스 구현 -
Random#bytes
메서드 추가 -
String#crypt
비추천 -
Object#=~
비추천 -
Array#select
의 별칭으로Array#filter
추가 -
Array#|
의 별칭으로Array#union
추가 - 합집합 -
Array#-
의 별칭으로Array#difference
추가 - 차집합 -
Binding#source_location
추가 -binding
호출한 위치의 파일과 행을 출력 -
Dir#each_child
추가 -Dir#children
의 반복자 -
FileUtils#cp_lr
추가 - 대상 경로의 파일을 재귀적으로 하드 링크 -
Matrix
클래스 관련 개선
자세한 내용은 2.6 릴리즈 페이지와 루비 저장소의 NEWS 문서를 참고해주세요. (일본어입니다만) 루비 커미터 사사다 고이치 씨와 엔도 유스케 씨가 정리한 쿡패드 블로그의 프로가 해설하는 Ruby 2.6 NEWS 파일도 이번 변경사항을 이해하는 데 큰 도움이 됩니다.