Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

RBS 이해, Rubys의 새로운 유형 주석 시스템

RBS는 Ruby의 새로운 유형 구문 형식 언어의 이름입니다. RBS를 사용하면 .rbs라는 새 확장자를 가진 파일의 Ruby 코드에 유형 주석을 추가할 수 있습니다. . 다음과 같이 생겼습니다.

class MyClass
  def my_method : (my_param: String) -> String
end

RBS와 함께 유형 주석을 제공하면 다음과 같은 이점을 얻을 수 있습니다.

  • 코드베이스의 구조를 정의하는 깔끔하고 간결한 방법
  • 클래스를 직접 변경하는 대신 파일을 통해 레거시 코드에 유형을 추가하는 더 안전한 방법입니다.
  • 정적 및 동적 유형 검사기와 보편적으로 통합할 수 있는 가능성
  • 메서드 오버로딩, 덕 타이핑, 동적 인터페이스 등을 처리하는 새로운 기능

하지만 기다려! Sorbet 및 Steep과 같은 정적 유형 검사기가 이미 있지 않습니까?

예, 그들은 훌륭합니다! 그러나 4년 간의 토론과 소수의 커뮤니티 구축 유형 검사 후에 Ruby 커미터 팀은 유형 검사 도구를 구축하기 위한 몇 가지 표준을 정의해야 할 때라고 생각했습니다.

RBS는 공식적으로 언어이며 Ruby 3와 함께 현실화되고 있습니다.

또한 Ruby의 동적으로 유형이 지정된 특성과 Duck Typing 및 메서드 오버로딩과 같은 일반적인 패턴으로 인해 가능한 최상의 접근 방식을 보장하기 위해 몇 가지 예방 조치가 시행되고 있습니다. 이에 대해서는 곧 자세히 살펴보겠습니다.

루비 3 설치

여기에 표시된 예를 따르려면 Ruby 3를 설치해야 합니다.

공식 지침을 따르거나 많은 Ruby 버전을 관리해야 하는 경우 ruby-build를 통해 수행할 수 있습니다.

또는 rbs를 설치할 수도 있습니다. 현재 프로젝트에 직접 gem:

gem install rbs

정적 입력과 동적 입력

더 진행하기 전에 이 개념을 명확히 합시다. 동적으로 유형이 지정된 언어는 정적 언어와 어떻게 비교됩니까?

Ruby 및 JavaScript와 같은 동적으로 유형이 지정된 언어에는 런타임 중에 금지된 작업이 발생하는 경우 인터프리터가 처리 방법을 이해할 수 있도록 사전 정의된 데이터 유형이 없습니다.

이는 정적 타이핑에서 예상되는 것과 반대입니다. Java 및 C와 같은 정적으로 유형이 지정된 언어는 컴파일 시간 동안 유형을 확인합니다.

다음 Java 코드 스니펫을 참조로 사용하십시오.

int number = 0;
number = "Hi, number!";

두 번째 줄에서 오류가 발생하므로 정적 입력에서는 불가능합니다.

error: incompatible types: String cannot be converted to int

이제 Ruby에서 동일한 예를 살펴보세요.

number = 0;
number = "Hi, number!";
puts number // Successfully prints "Hi, number!"

Ruby에서 변수 유형은 즉석에서 다양합니다. 즉, 인터프리터가 서로를 동적으로 바꾸는 방법을 알고 있음을 의미합니다.

이 개념은 일반적으로 강력한 형식과 혼동됩니다. 대 약한 유형 언어.

Ruby는 동적일 뿐만 아니라 강력한 유형의 언어입니다. 즉, 런타임 중에 변수가 유형을 변경할 수 있습니다. 그러나 미친 유형의 믹싱 작업을 수행할 수는 없습니다.

이전 예제에서 수정한 다음 예제(Ruby)를 살펴보세요.

number = 2;
sum = "2" + 2;
puts sum

이번에는 서로 다른 유형(IntegerString ). Ruby에서 다음 오류가 발생합니다.

main.rb:2:in `+': no implicit conversion of Integer into String (TypeError)
  from main.rb:2:in `<main>'

다시 말해, Ruby는 (아마도) 다른 유형을 포함하는 복잡한 계산을 수행하는 작업은 모두 당신의 몫이라고 말합니다.

반면에 JavaScript는 약하게 동적으로 입력되며 동일한 코드에서 다른 결과를 허용합니다.

> number = 2
  sum = "2" + 2
  console.log(sum)
> 22 // it concatenates both values

RBS는 소르베와 어떻게 다릅니까?

첫째, 각 사람이 코드에 주석을 달 때 취하는 접근 방식에서. Sorbet은 코드 전체에 명시적으로 주석을 추가하여 작동하지만 RBS는 .rbs가 있는 새 파일을 생성하기만 하면 됩니다. 확장자.

이것의 주요 이점은 레거시 코드베이스의 마이그레이션에 대해 생각할 때입니다. 원본 파일은 영향을 받지 않으므로 RBS 파일을 프로젝트에 채택하는 것이 훨씬 안전합니다.

제작자에 따르면 RBS의 주요 목표는 구조를 설명하는 것입니다. 당신의 루비 프로그램의. 클래스/메서드 서명만 정의하는 데 중점을 둡니다.

RBS 자체는 검사를 입력할 수 없습니다. 그 목표는 형식 검사기(Sorbet 및 Steep과 같은)가 작업을 수행하기 위한 기반으로 구조를 정의하는 것으로 제한됩니다.

간단한 Ruby 상속의 예를 살펴보겠습니다.

class Badger
    def initialize(brand)
      @brand = brand
    end

    def brand?
      @brand
    end
end

class Honey < Badger
  def initialize(brand: "Honeybadger", sweet: true)
    super(brand)
    @sweet = sweet
  end

  def sweet?
    @sweet
  end
end

엄청난! 몇 가지 속성과 유추된 유형이 있는 두 개의 클래스만 있으면 됩니다.

아래에서 가능한 RBS 표현을 볼 수 있습니다.

class Brand
  attr_reader brand : String

  def initialize : (brand: String) -> void
end

class Honey < Brand
  @sweet : bool

  def initialize : (brand: String, ?sweet: bool) -> void
  def sweet? : () -> bool
end

꽤 비슷하지 않나요? 여기서 가장 큰 차이점은 유형입니다. initialize Honey 메소드 예를 들어 클래스는 하나의 String을 받습니다. 및 하나의 boolean 매개변수이고 아무것도 반환하지 않습니다.

한편 Sorbet 팀은 RBI(Sorbet의 유형 정의에 대한 기본 확장)와 RBS 간의 상호 운용성을 허용하는 도구를 만들기 위해 긴밀히 협력하고 있습니다.

목표는 Sorbet 및 모든 유형 검사기가 RBS의 유형 정의 파일을 사용하는 방법을 이해할 수 있도록 토대를 마련하는 것입니다.

비계 도구

언어로 시작하고 이미 진행 중인 프로젝트가 있는 경우 입력을 시작하는 위치와 방법을 추측하기 어려울 수 있습니다.

이를 염두에 두고 Ruby 팀은 rbs라는 매우 유용한 CLI 도구를 제공했습니다. 기존 클래스의 스캐폴드 유형으로.

사용 가능한 명령을 나열하려면 rbs help를 입력하세요. 콘솔에서 결과를 확인하십시오:

RBS 이해, Rubys의 새로운 유형 주석 시스템 CLI 도구에서 사용 가능한 명령

아마도 목록에서 가장 중요한 명령은 prototype일 것입니다. param으로 제공된 소스 코드 파일의 AST를 분석하여 "대략적인" RBS 코드를 생성하기 때문입니다.

100% 효과가 없기 때문에 대략적입니다. 레거시 코드는 기본적으로 유형이 지정되지 않기 때문에 대부분의 스캐폴드 콘텐츠는 동일한 방식으로 제공됩니다. 예를 들어 명시적 할당이 없으면 RBS는 일부 유형을 추측할 수 없습니다.

이번에는 계단식 상속에서 세 가지 다른 클래스를 포함하는 다른 예를 참조로 들어 보겠습니다.

class Animal
    def initialize(weight)
      @weight = weight
    end

    def breathe
      puts "Inhale/Exhale"
    end
end

class Mammal < Animal
    def initialize(weight, is_terrestrial)
      super(weight)
      @is_terrestrial = is_terrestrial
    end

    def nurse
      puts "I'm breastfeeding"
    end
end

class Cat < Mammal
    def initialize(weight, n_of_lives, is_terrestrial: true)
        super(weight, is_terrestrial)
        @n_of_lives = n_of_lives
    end

    def speak
        puts "Meow"
    end
end

속성과 메서드가 있는 단순한 클래스입니다. 그 중 하나에는 기본 부울 값이 제공되며, 이는 RBS가 스스로 추측할 때 무엇을 할 수 있는지 보여주는 데 중요합니다.

이제 이러한 유형을 스캐폴드하기 위해 다음 명령을 실행해 보겠습니다.

rbs prototype rb animal.rb mammal.rb cat.rb

원하는 만큼 Ruby 파일을 전달할 수 있습니다. 다음은 이 실행의 결과입니다.

class Animal
  def initialize: (untyped weight) -> untyped

  def breathe: () -> untyped
end

class Mammal < Animal
  def initialize: (untyped weight, untyped is_terrestrial) -> untyped

  def nurse: () -> untyped
end

class Cat < Mammal
  def initialize: (untyped weight, untyped n_of_lives, ?is_terrestrial: bool is_terrestrial) -> untyped

  def speak: () -> untyped
end

예상대로 RBS는 우리가 클래스를 만들 때 목표로 삼았던 대부분의 유형을 이해할 수 없습니다.

대부분의 작업은 untyped를 수동으로 변경하는 것입니다. 실제에 대한 참조. 이를 달성하기 위한 더 나은 방법을 찾기 위한 몇 가지 논의가 현재 커뮤니티에서 진행 중입니다.

메타프로그래밍

메타프로그래밍의 경우 rbs 도구는 동적 특성으로 인해 별로 도움이 되지 않습니다.

다음 수업을 예로 들어 보겠습니다.

class Meta
    define_method :greeting, -> { puts 'Hi there!' }
end

Meta.new.greeting

다음은 이 유형을 스캐폴딩한 결과입니다.

class Meta
end

오리 타이핑

Ruby는 객체의 본성(유형)에 대해 그다지 걱정하지 않지만 객체가 할 수 있는 것(무엇을 하는지)에 대해서는 관심을 갖습니다.

오리 타이핑은 모토에 따라 작동하는 유명한 프로그래밍 스타일입니다.

"물체가 오리처럼 행동하면(말하기, 걷기, 날기 등) 오리입니다."

즉, 원래 정의와 유형이 오리를 나타내지 않아도 Ruby는 항상 오리처럼 취급합니다.

그러나 덕 타이핑은 쉽게 까다로워지고 찾거나 읽기 어려울 수 있는 코드 구현의 세부 사항을 숨길 수 있습니다.

RBS는 인터페이스 유형 개념을 도입했습니다. , 구체적인 클래스나 모듈에 의존하지 않는 메소드 세트입니다.

이전 동물 상속 예제를 사용하여 Cat 상속 대상:

class Terrestrial < Animal
    def initialize(weight)
        super(weight)
    end

    def run
        puts "Running..."
    end
end

비 지상파 자식 개체가 실행되는 것을 방지하기 위해 이러한 작업에 대한 특정 유형을 확인하는 인터페이스를 만들 수 있습니다.

interface _CanRun
  # Requires `<<` operator which accepts `Terrestrial` object.
  def <<: (Terrestrial) -> void
end

RBS 코드를 특정 실행 방법에 매핑할 때 서명은 다음과 같습니다.

def run: (_CanRun) -> void

누군가가 Terrestrial 개체 이외의 다른 개체를 메서드에 전달하려고 할 때마다 유형 검사기가 오류를 기록하는지 확인합니다.

통합 유형

또한 Rubyists는 다양한 유형의 값을 포함하는 표현식을 사용하는 것이 일반적입니다.

def fly: () -> (Mammal | Bird | Insect)

RBS는 단순히 파이프를 통해 결합함으로써 공용체 유형을 수용합니다. 연산자.

메서드 오버로딩

(많은 프로그래밍 언어 중에서 실제로) 또 다른 일반적인 관행은 메서드 오버로딩을 허용하는 것입니다. 여기서 클래스는 동일한 이름을 가진 메서드를 두 개 이상 가질 수 있지만 매개변수의 유형이나 수, 순서 등은 시그니처가 다릅니다. ).

동물이 가장 가까운 진화적 사촌을 반환할 수 있는 예를 들어 보겠습니다.

def evolutionary_cousins: () -> Enumerator[Animal, void] | { (Animal) -> void } -> void

이러한 방식으로 RBS를 사용하면 주어진 동물에 단일 진화적 사촌이 있는지 아니면 여러 마리가 있는지 명시적으로 결정할 수 있습니다.

타입프로프

이와 동시에 Ruby 팀은 RBS 콘텐츠를 분석하고 생성하는 것을 목표로 하는 실험적 유형 수준 Ruby 인터프리터인 typeprof라는 새로운 프로젝트도 시작했습니다.

추상적인 해석으로 작동하며 더 나은 개선을 위한 첫 번째 단계를 아직 진행 중이므로 프로덕션 용도로 사용할 때는 주의해야 합니다.

설치하려면 프로젝트에 gem을 추가하기만 하면 됩니다.

gem install typeprof

2.7 이상의 Ruby 버전이 필요합니다.

다음 버전의 Animal 가져오기 클래스:

class Animal
    def initialize(weight)
      @weight = weight
    end

    def die(age)
      if age > 50
        true
      elsif age <= 50
        false
      elsif age < 0
        nil
      end
    end
end

Animal.new(100).die(65)

age 메서드 내부에서 진행 중인 작업을 기반으로 합니다. 동일한 메서드를 추가로 호출하면 TypeProf는 코드에서 조작된 유형을 현명하게 유추할 수 있습니다.

typeprof animal.rb를 실행할 때 명령이 출력되어야 합니다.

## Classes
class Animal
  @weight: Integer

  def initialize: (Integer weight) -> Integer
  def die: (Integer age) -> bool?
end

이미 많은 코드가 진행 중인 프로젝트에 제공할 수 있는 강력한 도구입니다.

VS 코드 통합

현재 형식 지정, 구조 검사 등을 위해 RBS를 처리하는 데 사용할 수 있는 VS Code 플러그인이 많지 않습니다. 특히 아직 비교적 새롭기 때문입니다.

그러나 상점에서 "RBS"를 검색하면 ruby-signature라는 플러그인을 찾을 수 있습니다. 아래와 같이 구문 강조 표시에 도움이 됩니다.

RBS 이해, Rubys의 새로운 유형 주석 시스템 VS Code의 RBS 구문 강조 표시

결론

RBS는 매우 신선하며 이미 더 안전한 Ruby 코드베이스를 향한 중요한 단계를 나타냅니다.

일반적으로 시간이 지나면 Ruby on Rails 애플리케이션용 RBS 파일 생성을 위한 RBS Rails와 같은 새로운 도구와 오픈 소스 프로젝트가 이를 뒷받침할 것입니다.

더 안전하고 버그가 없는 애플리케이션으로 Ruby 커뮤니티의 미래는 놀라운 일입니다. 보고싶어요!