Crystal-Lang is ❤️

저는 최근에 Crystal-lang을 즐기고 있습니다. 간단한 토이 프로젝트부터 Noir란 사이즈가 점점 커지고 있는 프로젝트까지 Crystal을 통해 구현하고 있습니다. 오늘은 제가 Crystal을 좋아하게된 이유에 대해 이야기하려고 합니다.

What is Crystal?

먼저 Crystal에 대해 가볍게 정리하고 시작합니다. Crystal은 Ruby와 유사한 문법을 가지는 컴파일, 정적 타입의 언어입니다. Rubyist라면 거의 바로 적응할 수 있을 만큼의 문법 친화도를 가지며 Go, Clojure에서 사용하는 채널 기반의 통신을 기본적으로 지원함으로써 안정적인 동시성 모델을 가진다고 생각합니다.

A language for humans and computers

위 내용은 언어의 핵심 방향인데, Cyrstal 이 가고자 하는 방향을 느낄 수 있습니다. Ruby에서 추구하는 인간 친화적인 언어을 유지하면서 성능 등 컴퓨터를 위한 부분도 챙긴다는 느낌을 받았습니다.

Rubyist/Crystalist

Ruby와 Crystal의 문법적 친화도는 굉장히 높습니다. 이것이 의미하는 내용은 Ruby를 사용하던 Rubyist는 Crystal을 쉽게 사용할 수 있다는 이야기입니다. 저 또한 Crystal을 처음 써봤을 때에도 크게 부담이 없었습니다.

ruby-crystal Ruby And Crystal

그래서 많은 Rubyist들이 Crystal을 병행해서 사용하는 것을 볼 수 있습니다. 다만 안타까운 점 중 하나는 많은 Ruby 유저들은 언어의 인기나 유행과 다르게 Ruby 자체에 대한 애정이 높다는 점입니다. Crystal은 충분히 흥미를 끌어낼 순 있었지만, Rubyist가 Crystalist로 전향하기는 어려울거라 생각했고 현재도 동일하다고 생각합니다. (둘 다 쓰면 되죠!)

Performance

Crystal은 Compiled Language입니다. Ruby, Python 같이 별도의 인터프리터가 아닌 컴파일러를 통해 바이너리를 빌드하고 실행하기 때문에 당연히 속도적인 이점이 큽니다. 개인적으로 Golang도 많이 사용하는데, 위에서 이야기한 문법적인 이야기 때문에 코드 작성 자체는 Crystal이 훨씬 편한 느낌을 받습니다. 여기에 속도까지 챙길 수 있으니 큰 이점으로 다가왔고, 실제로 제가 Golang을 주로 사용하던 목적(CLI 도구 개발)을 Crystal이 커버하게 되었습니다.

여러 Benchmark를 보면 상위권에 잘 들어가는 편입니다 :D

TDD with Spec

개인적으로 TDD를 온전하게 따르지는 못하지만, 가급적 챙기고 Test에 용이하게 개발하려고 노력하는 편입니다. 다른 언어들도 비슷하지만, Crystal은 자체적으로 Testing 기능을 가지고 있습니다. crystal spec 이란 명령으로 실행할 수 있으며 _spec 으로 끝나는 파일들을 대상으로 테스트를 진행합니다. 재미있는 건 이러한 spec 기능의 문법과 스타일은 Ruby의 유명한 테스팅 프레임워크인 RSpec과 유사합니다.

describe "Detect Go Echo" do
  options = default_options()
  instance = DetectorGoEcho.new options

  it "go.mod" do
    instance.detect("go.mod", "github.com/labstack/echo").should eq(true)
  end
end

Macro

Crystal은 macro를 지원합니다. 이는 Metaprogramming을 할 수 있도록 도우며, 코드 작성 자체도 굉장히 간소하게 줄여줍니다.

macro defind_detectors(detectors)
  {% for detector, index in detectors %}
    instance = {{detector}}.new(options)
    instance.set_name
    detector_list << instance
  {% end %}
end

비슷한 기능을 가진 다른 언어들도 있지만 개인적으로 굉장히 직관적인 코드를 보여줘서 좋다고 생각합니다.

class Object
  def has_instance_var?(name) : Bool
    {{ @type.instance_vars.map &.name.stringify }}.includes? name
  end
end

person = Person.new "John", 30
person.has_instance_var?("name") #=> true
person.has_instance_var?("birthday") #=> false

Overcoming Limits

모든 언어가 그렇듯 완벽하진 못합니다. Crystal도 한계점이 명확하게 있고 이를 극복해야 좀 더 나은 언어로 발전될 수 있을 것 같습니다. 개인적으로 Community가 좀 더 활성화되고 Echosystem이 강해졌으면 하는 바람이 있습니다. 어떻게 보면 Python의 echosystem을 포용할 수 있는 Mojo가 정말 대단한 언어란 생각이 들기도 하네요.

결국 바람이 이루어지려면 Crystal을 사용하는 개발자가 노력해야 할 부분이겠네요.

Conclusion

사실 여러 가지 이유를 나눠서 설명했지만, 궁극적으로는 재미가 가장 큰 이유입니다. 제가 Ruby를 벗어나지 못한 이유기도 했으며, Ruby와 Crystal로 코드를 작성할 때가 가장 즐겁기에 두 언어를 좋아하는 겁니다. (물론 Go도 좋아합니다. 요즘 손이 덜 가서 그렇지 😆)