범위의 이야기

11515 단어 Ruby
Ruby Advent Calendar 2020의 23일째.
내부적으로 어려운 말을 할 수 없기 때문에 문서를 바탕으로 뭔가를 쓰면 좋겠다고 생각했는데 마침 루비의 버전 업그레이드에서 Range를 사용하는 처리 행위가 바뀌는 상황을 만났고 이를 계기로 단말기가 없고 시작이 없는 Range를 조사했습니다.나는 그 일을 쓰고 싶다.

개요


Ruby2.6에서 끝 없음까지, Ruby2.7에서 끝 없음까지 사용할 수 있습니다.
각 버전에서 생성된 시작과 끝이 없는 범위가 재미있기 때문에 소개해 드리겠습니다.

끝 없음 및 끝 없음

1..  # 終端なし
..10 # 始端なし
시작도 끝도 생략할 수 없다.
..  # syntax error
그러나 시작 단말기는 없는 범위를 쓸 수 있다.
nil..nil

터미널이나 시작이nil일 때의 버전별 차이


이 버전의 실행 결과는 다음과 같다.
1..nil
#  < 2.6: ArgumentError
# >= 2.6: 1..

nil..10
#  < 2.7: ArgumentError
# >= 2.7: ..10

nil..nil
#  < 2.6: nil..nil
# ~> 2.6: nil..
# >= 2.7: nil..nil
처음의 두 가지 차이는 뚜렷하지만 마지막 것은 아직 이해하기 어렵다.
< 2.6: nil..nil
이것은 nil부터 nil까지의 범위이다.
크기는 nil, 포함 nil 뿐입니다.
range = nil..nil
range.size        #=> nil
range.cover?(nil) #=> true
~> 2.6: nil..
이것도 nil부터 시작된 단말기 없는 범위다.
여기의 사이즈도 nil, nil만 포함됩니다.
range = nil..nil
range.size        #=> nil
range.cover?(nil) #=> true
>= 2.7: nil..nil
마지막으로 여기는 처음에 말한 바와 같이 시단 단말기도 없는 범위이다.
크기는 Infinity 의 모든 내용을 포함합니다.
range = nil..nil
range.size         #=> Infinity
range.cover?(nil)  #=> true
range.cover?(1)    #=> true
range.cover?('s')  #=> true
range.cover?(true) #=> true
그나저나 마지막nil..nil은sizeInfinity이기 때문에nil....nil상황에서의size도 소개했지만 Range#size과는Array#size다른것으로단순히요소의개수를나타내는 것이 아니다.
('a'..'z').size      #=> nil
('a'..'z').to_a.size #=> 26
원소수를 나타내는 방법이지만 말단 시작단이 모두 Numeric인 자류 대상이나 nil을 제외하고는 nil로 되돌아간다.

범위 변경 불가


루비의 범위 클래스는immutable입니다.대상 자체를 파괴적으로 변경해서는 안 된다는 것이다.따라서 한 번에 생성된 범위의 대상이 가리키는 범위는 절대 변경할 수 없습니다.
이렇게 말하면 파괴적인 변경을 할 방법을 강구한다.
...하지만 많은 조사를 했지만 파괴적인 변경 수단은 없었다.

이별에 즈음하여 증언하다.

Range#beginRange#first,Range#endRange#last는 같은 설명으로 쓰여 있지만 시작이 없거나 단말기가 없는 경우 결과는 다르다.
Range#begin
(..10).begin #=> nil
(..10).first #=> RangeError
Range#end
(1..).end #=> nil
(1..).last #=> RangeError
소스 코드를 읽어보면 설치가 좀 다른 것 같아요.
# Range#begin
static VALUE
range_begin(VALUE range)
{
    return RANGE_BEG(range);
}
# Range#first
static VALUE
range_first(int argc, VALUE *argv, VALUE range)
{
    VALUE n, ary[2];

    if (NIL_P(RANGE_BEG(range))) {
        rb_raise(rb_eRangeError, "cannot get the first element of beginless range");
    }
    if (argc == 0) return RANGE_BEG(range);

    rb_scan_args(argc, argv, "1", &n);
    ary[0] = n;
    ary[1] = rb_ary_new2(NUM2LONG(n));
    rb_block_call(range, idEach, 0, 0, first_i, (VALUE)ary);

    return ary[1];
}
C언어는 전혀 알아볼 수 없지만 언뜻 보면 Range#firstRange#lastbegin에서 얻은 값이 end인 경우 예외가 발생한다.
이후nilRange#first 매개 변수를 전달하면 최초와 마지막을 기점으로 여러 개의 값을 얻을 수 있기 때문에 이 부분의 처리인 것 같습니다.
이 부근의 동작의 차이를 잘 설명할 수 있는 글을 생각해 내면 수정 PR을 작성한다.
참고로 여기의'edit'링크를 통해 PR을 만들 수 있습니다.
Range#last 반환Range#size의 경우도 있고, 단말기나 시단이 Numeric 하위 클래스 대상이 아니라면, nil 단말기1..nilnil 하위 클래스 대상이 아니지만, Numeric 반환nil은 아니다.여기의 해석이 어떤지 모르기 때문에 단순히 틀렸다고 할 수는 없지만 이해하기 쉬운 표현변경의 여지가 있는 것 같습니다.
Range#size

총결산


평소에 자주 사용하는 클래스의 대상도 문서를 다시 읽으면서 옆에서 이동하면 새로운 발견을 할 수 있어 즐겁다.
처음에 설명한 "Ruby 버전 업그레이드에서 Range를 사용한 처리 행위가 변경된 사태"에 대한 코드는 다음과 같습니다.
(time_or_nil..).cover?(time)
Time 객체의 값을 위의 끝 없음 범위Infinity와 비교하고 포함될 경우 반환합니다===.Range는 때때로 true 형식을 취하는데 이런 경우에는 반드시 nil.. 되돌아오지만 여기서 2.7로 올라갈 때는 반드시 되돌아온다 false.같은 상황을 겪는 사람이 있을지도 모르니 주의하세요.
개인이라면 괜찮지만 일이면 다른 사람이 쓴 코드를 파악하지 못하기 때문에 이런 버전 업그레이드는 행동을 바꾸는 부분을 특히 쉽게 볼 수 있다.행동이 변할 때 주울 수 있도록 평소에 시험을 잘 쓰고 싶어요.

참고 자료


활차 true의 값(1..nil).end이 단말기로 인식될 수 있는지 여부.여기nil는'단말기 없음'을 표시하기 때문에 단말기도 nil가 아니라'무'라고 할 수 있다. 

좋은 웹페이지 즐겨찾기