시길

시길 (Sigils)

Elixir는 더블쿼트 문자열과 함께, ~c"hello world"라는 시길(sigil) 문법으로 정의되는 charlist이라는 개념을 제공해요. 이번 장에서는 시길에 대해 더 자세히 배우고, 나만의 시길을 정의하는 방법도 알아볼게요.

출처: Elixir 공식 가이드

Elixir의 목표 중 하나는 확장성(extensibility)이에요. 개발자가 언어를 특정 도메인에 맞게 확장할 수 있어야 한다는 거죠. 시길은 언어를 커스텀 텍스트 표현으로 확장하는 기반을 제공해요. 시길은 물결표(~) 문자로 시작하고, 그 뒤에 소문자 한 글자 또는 대문자 한 글자 이상, 그리고 구분자(delimiter)가 이어져요. 선택적으로 마지막 구분자 뒤에 수정자(modifier)를 붙일 수 있어요.

정규 표현식 (Regular expressions)

Elixir에서 가장 흔한 시길은 정규 표현식을 만드는 ~r이에요.

# A regular expression that matches strings which contain "foo" or "bar":
iex> regex = ~r/foo|bar/
~r/foo|bar/
iex> "foo" =~ regex
true
iex> "bat" =~ regex
false

Elixir는 PCRE 라이브러리가 구현한 Perl 호환 정규 표현식(regex)을 제공해요. regex는 수정자도 지원해요. 예를 들어 i 수정자는 정규 표현식을 대소문자를 구분하지 않게 만들어요.

iex> "HELLO" =~ ~r/hello/
false
iex> "HELLO" =~ ~r/hello/i
true

다른 수정자들과 정규 표현식으로 지원되는 연산에 대한 자세한 내용은 Regex 모듈을 확인해 보세요.

지금까지 예제는 모두 /를 구분자로 사용했어요. 그런데 시길은 8가지 구분자를 지원해요.

~r/hello/
~r|hello|
~r"hello"
~r'hello'
~r(hello)
~r[hello]
~r{hello}
~r<hello>

다른 구분자를 지원하는 이유는 구분자를 이스케이프하지 않고도 리터럴을 쓸 수 있는 방법을 제공하기 위해서예요. 예를 들어 슬래시가 들어간 정규 표현식 ~(^https?://)~r/^https?:\/\//보다 읽기 좋아 보여요. 마찬가지로 정규 표현식에 슬래시와 캡처링 그룹(과 ()를 사용하는)이 있다면, 괄호 대신 더블쿼트를 선택할 수도 있어요.

문자열, charlist, 단어 목록 시길

Elixir는 텍스트 데이터 구조를 만드는 세 가지 시길을 기본으로 제공해요. 이 시길들은 리터럴 텍스트에 적절한 구분자를 고를 수 있게 해 줘서, 이스케이프를 신경 쓸 필요가 없어요.

문자열 (Strings)

~s 시길은 더블쿼트처럼 문자열을 만들 때 사용해요. ~s는 문자열 안에 더블쿼트가 들어 있을 때 유용해요.

iex> ~s(this is a string with "double" quotes, not 'single' ones)
"this is a string with \"double\" quotes, not 'single' ones"

Charlist

~c 시길은 charlist를 표현하는 정식 방법이에요.

iex> [?c, ?a, ?t]
~c"cat"
iex> ~c(this is a char list containing "double quotes")
~c"this is a char list containing \"double quotes\""

단어 목록 (Word lists)

~w 시길은 단어 목록(단어는 그냥 일반 문자열이에요)을 만들 때 사용해요. ~w 시길 안에서 단어는 공백으로 구분돼요.

iex> ~w(foo bar bat)
["foo", "bar", "bat"]

~w 시길은 결과 목록의 요소 데이터 타입을 지정하는 c, s, a 수정자(charlist, 문자열, 아톰을 각각 의미)도 받아들여요.

iex> ~w(foo bar bat)a
[:foo, :bar, :bat]

텍스트 시길에서의 보간과 이스케이프 (Interpolation and escaping in textual sigils)

시길은 문자 이스케이프와 보간(interpolation)을 처리하는 데도 도움을 줘요. 특히 대문자 텍스트 시길은 보간도 이스케이프도 수행하지 않으며, 대부분의 소문자 시길에는 대문자 변형이 있어요. 예를 들어 ~s~S는 둘 다 문자열을 돌려주지만, 전자는 이스케이프 코드와 보간을 허용하고 후자는 허용하지 않아요.

iex> ~s(String with escape codes \x26 #{"inter" <> "polation"})
"String with escape codes & interpolation"
iex> ~S(String without escape codes \x26 without #{interpolation})
"String without escape codes \\x26 without \#{interpolation}"

텍스트 시길에서 사용할 수 있는 이스케이프 코드는 다음과 같아요.

  • \\ – 백슬래시 하나
  • \a – 벨/알람
  • \b – 백스페이스
  • \d – delete
  • \e – escape
  • \f – form feed
  • \n – 줄바꿈
  • \r – 캐리지 리턴
  • \s – 공백
  • \t – 탭
  • \v – 세로 탭
  • \0 – 널 바이트
  • \xDD – 16진수로 된 단일 바이트를 나타냄(예: \x13)
  • \uDDDD, \u{D...} – 16진수로 된 Unicode 코드포인트를 나타냄(예: \u{1F600})

시길은 히어독(heredoc)도 지원해요. 즉 세 개의 더블쿼트 또는 세 개의 싱글쿼트를 구분자로 사용하는 거예요.

iex> ~s"""
...> this is
...> a heredoc string
...> """

히어독 시길의 가장 흔한 사용처는 문서를 작성할 때예요. 예를 들어 문서에 이스케이프 문자를 쓰면 일부 문자를 이중 이스케이프해야 하기 때문에 곧 오류가 나기 쉬워져요.

@doc """
Converts double-quotes to single-quotes.

## Examples

    iex> convert("\\\"foo\\\"")
    "'foo'"

"""
def convert(...)

~S를 사용하면 이 문제를 완전히 피할 수 있어요.

@doc ~S"""
Converts double-quotes to single-quotes.

## Examples

    iex> convert("\"foo\"")
    "'foo'"

"""
def convert(...)

달력 시길 (Calendar sigils)

Elixir는 다양한 시간과 날짜 형식을 다루는 시길을 제공해요.

날짜 (Date)

%Date{} 구조체는 year, month, day, calendar 필드를 담아요. ~D 시길로 만들 수 있어요.

iex> d = ~D[2019-10-31]
~D[2019-10-31]
iex> d.day
31

시간 (Time)

%Time{} 구조체는 hour, minute, second, microsecond, calendar 필드를 담아요. ~T 시길로 만들 수 있어요.

iex> t = ~T[23:00:07.0]
~T[23:00:07.0]
iex> t.second
7

NaiveDateTime

%NaiveDateTime{} 구조체는 Date와 Time의 필드를 모두 담아요. ~N 시길로 만들 수 있어요.

iex> ndt = ~N[2019-10-31 23:00:07]
~N[2019-10-31 23:00:07]

왜 naive라고 부를까요? 시간대(timezone) 정보를 담고 있지 않기 때문이에요. 그래서 주어진 날짜시간이 어떤 시간대에서는 아예 존재하지 않거나 두 번 존재할 수 있어요. 예를 들어 서머타임을 위해 시계를 앞뒤로 움직일 때죠.

UTC DateTime

%DateTime{} 구조체는 NaiveDateTime과 같은 필드에 시간대를 추적하는 필드를 추가로 담아요. ~U 시길은 UTC 시간대의 DateTime을 만들 수 있게 해 줘요.

iex> dt = ~U[2019-10-31 19:59:03Z]
~U[2019-10-31 19:59:03Z]
iex> %DateTime{minute: minute, time_zone: time_zone} = dt
~U[2019-10-31 19:59:03Z]
iex> minute
59
iex> time_zone
"Etc/UTC"

커스텀 시길 (Custom sigils)

이 장의 시작에서 언급했듯이 Elixir의 시길은 확장 가능해요. 실제로 ~r/foo/i 시길을 쓰는 것은 sigil_r을 바이너리와 charlist를 인자로 호출하는 것과 같아요.

iex> sigil_r(<<"foo">>, [?i])
~r"foo"i

h sigil_r~r 시길의 문서에 접근할 수 있어요.

iex> h sigil_r
...

sigil_{character} 패턴을 따르는 함수를 구현하면 나만의 시길도 만들 수 있어요. 예를 들어 정수를 돌려주는 ~i 시길(선택적인 n 수정자로 음수를 만들 수 있게)을 구현해 볼게요.

iex> defmodule MySigils do
...>   def sigil_i(string, []), do: String.to_integer(string)
...>   def sigil_i(string, [?n]), do: -String.to_integer(string)
...> end
iex> import MySigils
iex> ~i(13)
13
iex> ~i(42)n
-42

커스텀 시길은 소문자 한 글자이거나, 대문자 한 글자 뒤에 대문자와 숫자가 이어지는 형태일 수 있어요. 실제로는 템플릿 언어를 임베드하거나, 심지어 Elixir 안에서 정규 언어를 표현하는 데 자주 사용돼요. 더 자세히 알고 싶다면, sigil_* 함수/매크로가 정의된 Kernel 모듈에서 시길이 어떻게 구현되는지 확인하는 것을 시작점으로 삼아 보세요.

더 알아보기