IO

IO

입출력(IO)을 다루는 함수들의 모듈이에요. 이 모듈의 많은 함수는 인자로 IO 장치(IO device)를 기대해요. IO 장치는 PID이거나 프로세스를 나타내는 아톰이어야 해요. 편의상 Elixir는 Erlang의 :standard_io:standard_error의 지름길로 :stdio:stderr를 제공해요.

대부분의 함수는 chardata를 기대해요. 다른 타입이 주어지면 함수는 String.Chars 프로토콜을 통해 그 타입을 문자열로 변환해요(typespec에 나와 있어요). chardata에 대한 자세한 내용은 아래 "IO data" 절을 보세요. 이 모듈의 함수는 가능한 한 UNIX 스타일 이름을 사용해요.

출처: IO

본문

IO 장치 (IO devices)

IO 장치는 아톰이거나 PID일 수 있어요. 아톰이면 반드시 등록된 프로세스의 이름이어야 해요. 게다가 Elixir는 두 가지 지름길을 제공해요.

  • :stdio:standard_io의 지름길로, Erlang의 현재 Process.group_leader/0에 매핑돼요.
  • :stderr — Erlang이 제공하는 :standard_error라는 이름의 프로세스의 지름길이에요.

IO 장치는 자신의 위치(position)를 유지해요. 즉 이후 읽기·쓰기 함수 호출은 장치가 마지막으로 접근된 지점부터 시작돼요. 파일의 위치는 :file.position/2 함수로 바꿀 수 있어요.

IO data

IO data는 어떤 상황에서 binary보다 더 효율적인 대안으로 쓸 수 있는 자료 타입이에요. IO data 타입의 term은 binary이거나, 바이트(0..255 범위의 정수) 또는 중첩된 IO data를 담은 리스트예요. 타입은 재귀적이에요. binary "hello"를 나타내는 IO data의 예시 하나를 볼게요.

[?h, "el", ["l", [?o]]]

내장 iodata/0 타입은 iolist/0로 정의돼요. IO list는 IO data와 같지만 최상위에 binary를 허용하지 않아요(리스트 자체 안에는 binary가 여전히 허용돼요).

IO data의 사용 사례 (Use cases for IO data)

IO data가 존재하는 이유는, 더 큰 binary를 만들기 위해 더 작은 binary 조각에 append 연산을 많이 해야 하는 경우가 있기 때문이에요. 하지만 Erlang과 Elixir에서 binary를 연결하면 연결된 binary를 새 binary로 복사해요.

def email(username, domain) do
  username <> "@" <> domain
end

이 함수에서 email 주소를 만들면 usernamedomain binary를 복사해요. 이제 결과 email을 또 다른 binary 안에서 사용하고 싶다고 상상해 보세요.

def welcome_message(name, username, domain) do
  "Welcome #{name}, your email is: #{email(username, domain)}"
end

IO.puts(welcome_message("Meg", "meg", "example.com"))
#=> "Welcome Meg, your email is: [email protected]"

binary를 연결하거나 보간(#{})을 쓸 때마다 그 binary의 복사본을 만들어요. 하지만 많은 경우 완전한 binary가 필요한 건 만들 때가 아니라, 마지막에 출력하거나 어딘가로 보낼 때예요. 그런 경우 IO data를 만들어 binary를 구성할 수 있어요.

def email(username, domain) do
  [username, ?@, domain]
end

def welcome_message(name, username, domain) do
  ["Welcome ", name, ", your email is: ", email(username, domain)]
end

IO.puts(welcome_message("Meg", "meg", "example.com"))
#=> "Welcome Meg, your email is: [email protected]"

IO data를 만드는 건 binary를 연결하는 것보다 저렴해요. IO data의 여러 조각을 연결하는 건 단지 리스트 안에 함께 넣으면 되는데, IO data는 임의로 중첩될 수 있고 그건 저렴하고 효율적인 연산이기 때문이에요. :gen_tcpIO 같은 IO 기반 API 대부분은 IO data를 받아 binary로 변환하지 않고 소켓에 직접 써요.

IO data의 한 단점은 binary처럼 IO data 조각의 첫 부분에 패턴 매치를 할 수 없다는 거예요. 보통 IO data의 형태를 모르기 때문이에요. 그런 경우 iodata_to_binary/1을 호출해 binary로 변환할 수 있는데, C로 네이티브 구현돼 상당히 효율적이에요. IO data의 길이 계산 같은 다른 기능은 iodata_length/1을 호출해 iodata 위에서 직접 계산할 수 있어요.

Chardata

Erlang과 Elixir에는 chardata/0라는 개념도 있어요. Chardata는 IO data와 아주 비슷해요. 유일한 차이는 IO data의 정수는 바이트를 나타내는 반면 chardata의 정수는 Unicode 코드 포인트를 나타낸다는 거예요. 바이트(byte/0)는 0..255 범위의 정수이고, Unicode 코드 포인트(char/0)는 0..0x10FFFF 범위의 정수예요. IO 모듈은 IO data용 iodata_to_binary/1 함수의 "대응편"으로 chardata용 chardata_to_string/1 함수를 제공해요.

chardata에 iodata_to_binary/1을 쓰면 인자 오류(argument error)가 나요. 예를 들어 한 바이트로 표현할 수 없는 코드 포인트 를 IO data 안에 넣어 보려고 해요.

IO.iodata_to_binary(["The symbol for pi is: ", ?π])
#=> ** (ArgumentError) argument error

대신 chardata를 쓰면 예상대로 동작해요.

iex> IO.chardata_to_string(["The symbol for pi is: ", ?π])
"The symbol for pi is: π"

더 알아보기

  • IO.StreamIO.stream/2IO.binstream/2가 반환하는 구조체
  • IO.ANSI — ANSI 이스케이프 시퀀스 렌더링
  • String.Chars — 값을 문자열로 변환하는 프로토콜
  • :file.position/2 — IO 장치 위치 변경