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 주소를 만들면 username과 domain 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_tcp나 IO 같은 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.Stream —
IO.stream/2와IO.binstream/2가 반환하는 구조체 - IO.ANSI — ANSI 이스케이프 시퀀스 렌더링
- String.Chars — 값을 문자열로 변환하는 프로토콜
- :file.position/2 — IO 장치 위치 변경