이진 데이터를 다루는 일은 까다로울 수 있어요. 그래서 Gleam은 BitArray 타입과 함께, 이진 데이터를 만들고 패턴 매칭할 수 있는 문법을 제공해요.
비트 배열 리터럴은 <<>> 문법으로 정의해요. 비트 배열 리터럴은 세그먼트 단위로 정의하는데, 각 세그먼트는 값과 어노테이션으로 이루어지고 둘은 :로 구분해요. 어노테이션은 값을 인코딩하는 데 몇 비트를 쓸지 지정해요. 어노테이션은 완전히 생략할 수도 있는데, 그러면 기본값으로 8비트 정수 값이 돼요.
// This defines a bit array with three segments of a single bit each
<<0:1, 1:1, 0:1>>
// This defines a bit array with three segments of 8 bits each
<<0, 1, 0>>
타입을 :1로 지정하는 것은 :size(1)을 쓰는 축약형이에요. 비트 크기가 변수에서 온다면 더 긴 문법을 사용해야 해요.
let segment_size = 1
<<0:size(segment_size), 1:size(segment_size), 0:size(segment_size)>>
이진 정수 리터럴을 작성할 때는 리터럴 앞에 0b를 붙여서 2진법 표기로 바로 쓸 수 있어요. 참고로, 테스트나 프로그램에서 출력될 때는 십진수로 표시돼요.
<<0b1011:4>> == <<11:4>>
// -> True
세그먼트의 값이 세그먼트 타입이 담을 수 있는 용량을 넘으면, 왼쪽부터 잘려 나가요.
<<0b1011:3>> == <<0b0011:3>>
// -> True
비트 배열 문법을 사용하면 기존 비트 배열의 앞과 뒤에 모두 값을 추가할 수 있어요. 기존 비트 배열에는 :bits 어노테이션을 반드시 붙여야 해요.
let value = <<0b110:3, 0b001:3>>
let new_value = <<0b011:3, value:bits, 0b000:3>>
// -> <<120, 8:size(4)>>
변수에 담긴 비트 배열은 이 문법으로 연결할 수 있어요. 크기가 변수인 두 비트 배열을 연결할 때는 :bits 어노테이션을 반드시 붙여야 해요.
let first = <<0b110:3>>
let second = <<0b001:3>>
let concatenated = <<first:bits, second:bits>>
// -> <<49:size(6)>>
패턴 매칭으로 비트 배열에서 값을 꺼낼 수도 있어요. 캡처하려는 각 조각의 비트 수를 알아야 하는데, 예외가 하나 있어요. :bits 어노테이션을 사용하면 크기를 모르는 비트 배열에도 패턴 매칭할 수 있지만, 이 방법은 마지막 조각에만 쓸 수 있어요.
let assert <<value:4, rest:bits>> = <<0b01101001:8>>
value == 0b0110
// -> true
비트 배열은 만들 때 사용한 형식과 다른 형식으로 출력될 수 있어요. 이 점은 비트 배열을 배울 때 자주 혼란을 일으켜요.
기본적으로 비트 배열은 다른 크기의 조각으로 만들었더라도 8비트(1바이트) 조각으로 표시돼요.
<<2011:11>>
// -> <<251, 3:size(3)>>
출력 가능한 UTF-8 인코딩 문자열을 나타내는 비트 배열을 만들면, Erlang을 대상으로 할 때 echo keyword를 사용해서 문자열로 표시할 수 있어요. 이는 Gleam이 내부적으로 문자열을 표현하는 방식의 구현 세부 사항 때문이에요.
<<>>
// -> ""
<<65, 66, 67>>
// -> "ABC"