Ruby3.0 타입이 들어왔다? (feat. RBS)

Tei·2020년 12월 26일
6
post-thumbnail

RUBY3.0

드디어 루비에 타입이 생겼다?

크리스마스의 전통대로 올해도 루비 의 새 버전이 릴리즈되었습니다. 향상된 성능, 매력적인 신기능이 추가되었는데요, 그중에서도 너무나도 반가운 소식인 타입 이 눈길을 사로잡습니다.

루비를 사용하며 가장 아쉬웠던게 타입이 없다는 점이었는데, 이번 업데이트를 통해 그 갈증이 많이 해소되지 않을까 싶습니다.

RBS

루비의 타입은 RBS 를 통해 기술한다고 합니다. README 에 보면

RBS is a language to describe the structure of Ruby programs. You can write down the definition of a class or module: methods defined in the class, instance variables and their types, and inheritance/mix-in relations. It also allows declaring constants and global variables.

라고 되어있네요.

간단히 해석해보자면,

  • 루비 프로그램의 모듈, 클래스, 함수,변수의 타입을 정의하기 위한 언어.
  • 상수와 전역변수에 대해서도 타입 설정이 가능하다.

TS <-> JS 의 관계랑은 조금 다른듯 합니다. TS 는 JS 에 타입을 추가한 새로운 언어인 반면, RBS 는 Ruby 파일은 그대로 두고, 타입의 정의만을 기술하는 RBS 파일을 추가해서 사용하네요.

언어에 타입이 추가되었다기보다는 타입 명세를 정의하고, 실행 전 타입을 미리 검사해서 문제를 막겠다! 에 가까운 늬앙스인것 같습니다.

그럼 간단하게 어떻게 사용하는지 알아보겠습니다.

RBS 의 문법

기본적으로 TS 같은 타이핑 방식을 사용합니다.

module ChatApp
  VERSION: String
	# 타입스크립트처럼 :타입 으로 타입을 나타낼 수 있음.
  class User
    attr_reader login: String
    attr_reader email: String

    # (인자) -> 리턴타입 으로 함수타입을 나타낼 수 있음. 
    # 아래처럼 (인자명: 타입) -> 리턴타입 이면 ruby 에서 넘길때도 login:"hi"
    # 이렇게 넘겨줘야 함. 
    def initialize: (login: String, email: String) -> void
  end

  class Message
    attr_reader from: User | Bot  # | 을 사용하여 SUM 타입 정의가능 
    attr_reader reply_to: Message? # `?` 은 옵셔널 타입. Nil 이어도 된다.

    def initialize: (from: User | Bot, string: String) -> void
    def reply: (from: User | Bot, string: String) -> Message
  end

  class Channel
    attr_reader name: String
    attr_reader messages: Array[Message]
    attr_reader users: Array[User]
    attr_reader bots: Array[Bot]

    def initialize: (name: String) -> void

    # `{`  `}` 는 블럭을 의미함. 
    # 메소드 오버로딩 가능. 
    # [타입1,타입2] 으로 제네릭처럼 사용가능.
    def each_member: () { (User | Bot) -> void } -> void  
                   | () -> Enumerator[User | Bot, void]
  end
end

전반적으로 TS 의 타입을 사용하셨다면, 무리없이 이해할 수 있는 방식입니다.

사용법

한번 사용해보려고 하니, 대체 어떻게 사용할 수 있는지
자세히 나와있지 않았습니다.

뭔가 TypeChecking 을 하는 커맨드가 있다던가... TS 처럼 빌드를 해야한다거나 공식으로 제공하는 방법이 있을것 같은데, 공식문서에는 따로 소개되어있지 않습니다.

그래서 일단은 steep 이라는 Gem 을 사용해서 돌려보도록 하겠습니다. 혹시 공식 방법을 아시는 분이 있다면 덧글로 알려주시면 감사하겠습니다.

(https://github.com/soutaro/steep)

Steep 설치

gem install steep

Steep 설정

steep init

위 명령어로 SteepFile 이 생성됩니다. 적절히 수정해봅시다.

target :app do
  check "testApp" # rb 파일들 경로
  signature "sig" # rbs 파일들의 경로

  library "set", "pathname"
end

testApp 디렉토리를 만들어서 rb 파일들을 넣어주고, sig 디렉토리를 만들어서 rbs 파일들 넣어줍니다.

최종 파일 상태.

├── Gemfile
├── Gemfile.lock
├── Steepfile
├── sig
│   └── rbs파일이름.rbs
└── testApp
    └── ruby파일이름.rb
직접 돌려보기

위의 예제는 조금 복잡하니, 간단한 helloWorld 클래스를 만들어 직접 돌려보도록 합시다.

# Practic.rbs

class Practice
    @print_str: String
    def initialize: (String) -> untyped
    def print: -> String
end

루비파일도 작성해봅니다.

# Practic.rb

class Practice
    def initialize(str) 
        @print_str = str 
    end

    def print
        return "#{@print_str} world!"
    end
end

이제 저 클래스를 만들고 돌려봅시다.

# run.rb

practice = Practice.new("hello")
p practice.print
실행결과

teihong@Teiui-MacBookPro: $ steep check
teihong@Teiui-MacBookPro: $

아무 오류도 뜨지 않았습니다. 그럼 한번 잘못된 타입을 넣어볼까요?

# run.rb

pr1 = Practice.new(12345)
p pr1.print
오류 투척

teihong@Teiui-MacBookPro:$ steep check
testApp/run.rb:1:26: ArgumentTypeMismatch: receiver=singleton(::Practice), expected=::String, actual=::Integer (12345)

오.. String 타입을 예상했는데, Integer 가 날라왔다고 합니다.

그럼 한번 SUM TYPE 으로 인자를 정의해서 String 혹은 Integer 를 받도록 해볼까요?

rbs 수정
# Practic.rbs 수정

class Practice
    @print_str: String | Integer
    def initialize: (String|Integer) -> untyped
    def print: -> String
end
짜잔

teihong@Teiui-MacBookPro$ steep check
teihong@Teiui-MacBookPro$

이제 Interger 타입도 잘 받는것을 확인할 수 있네요.

더 알아보기

rbs 의 문법은 여기에 정리되어있습니다.

(https://github.com/ruby/rbs/blob/master/docs/syntax.md)

현대의 타입언어에서 지원하는 대부분의 기능들을 지원해주는 것을 확인할 수 있습니다.

루비 3.0 에서는 타입 말고도 동시성을 위한 Ractor 라는 기능도 추가되었는데요,

좀더 기술이 성숙되면 Ractor 에 대해서도 한번 알아보면 좋을것 같네요.

마치며

ruby 3.0 릴리즈 기념으로 rbs 에 대해 알아보았습니다.

사실 TS 처럼 TypedRuby 가 나왔다고 생각했는데, 조금의 방향성 차이가 있는것 같습니다.

타입체킹을 한다는 점에서는 환영할만 하지만, 따로 파일을 분리해야한다는 점이 저에겐 다소 불편하게 느껴지기도 합니다.

특히 TS 의 경우 TS 가 엄청 똑똑해서, 개발시점에 타입추론을 잘 해주고 자동완성을 비롯한 편의적 기능을 많이 지원해주는데 RBS 는 현재로서는 개발 편의적인 기능들의 지원이 없는것 같네요.

또 아직은 자료도 많지 않아 루비의 타입이 프로덕션 환경에서 정착되기에는 조금 더 시간이 걸리지 않을까 하는 의견입니다.

하지만 루비에 타입이 들어온다는 것 자체가 너무나도 반가운 일이고 환영할만한 일입니다.
(이참에 루비를 쓰는 사람들이 많아지면 더 좋긴 하겠습니다... 애증의 언어 루비)

그럼 다음 글로 찾아뵙도록 하겠습니다.

profile
Being a service developer

2개의 댓글

comment-user-thumbnail
2021년 6월 14일

잘 보고 갑니당 :)

1개의 답글