열여섯번째 날: Boolean in Perl

저자

@JEEN_LEE - Homo Guichanius, Phaeburrism, tr/Something_/Something/

시작하며

펄로 몇 가지 제품을 개발하는 중 다른 언어/환경과 데이터를 주고받고 하는 와중에 회사 동료로부터 다음과 같은 이야기를 들었습니다.

펄은 Boolean 표기가 없냐? 왜 데이터가 전부 0 아니면 1이야? C냐?

그러고보니 그냥 익숙하게 true1, false0으로 사용하고 있었습니다. 아시다시피 펄의 기본 데이터 형에는 불리언(boolean)형이 없습니다. C도 마찬가지로 불리언이 없지요.

...
typedef enum {false, true} bool
...

하지만 앞의 예제처럼 C에서는 typedefenum을 사용해 불리언을 표현하기도 하죠. (아니, 그렇다면 펄에서도 CPAN의 enum 모듈을 쓰면 되는데!) 어찌되었든 이러한 연유로 JSON 같은 형식으로 데이터를 주고받을 때 is_*같은 불리언형의 필드에 0 또는 1을 전달하는 만행을 벌여왔었습니다.

{
  "people": [
    {
      "id": 0,
      "name": "Jeen Lee",
      "is_drunken": 0,
      "has_iphone6": 1
    },
    ...
  ]
}

늘 하듯이 펄에서 사용하던 식으로 데이터를 넘겨줬었죠. 하지만 그때마다 불리언형을 지원하는 쪽에서는 이런 데이터들의 형을 일일이 변환해줘야 하는 수고가 있었습니다. 미안하고 또 미안했습니다.

준비물

기사에서 다루는 모듈은 다음과 같습니다.

직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.

$ sudo cpan \
  boolean \
  Data::MessagePack \
  enum \
  JSON::MaybeXS \
  JSON::XS \
  overload \
  Types::Serialiser

사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.

$ cpan \
  boolean \
  Data::MessagePack \
  enum \
  JSON::MaybeXS \
  JSON::XS \
  overload \
  Types::Serialiser

그러면 어떻게?

나중에서야 이런 부분에 대한 배려가 부족했던 것을 반성하며 소잃고 외양간 고치기 격으로 알아보니, JSON::* 류의 JSON 인코딩/디코딩에서 \1/\0을 사용해 true/false를 넘길 수 있었습니다.

use JSON;

# {"is_jeen_drunken":true}
print JSON->new->encode({ is_jeen_drunken => \1 });

# {"is_jeen_homophobe":false}
print JSON->new->encode({ is_jeen_homophobe => \0 });

하지만 JSON의 불리언 표기를 위해 어떤 클래스의 접근자의 반환값을 \1, \0으로 해버리면 다음과 같은 문제가 발생합니다.

if (\0) { print "No~"  }  # No~
if (\1) { print "Yes~" }  # Yes~

print \0; # SCALAR(0x7fc6b2803b78)

\0는 스칼라의 주소값을 가지므로 조건문에서는 값이 있다고 간주하고 의도한 대로 동작하지 않습니다. 이런 문제를 해결하기 위해서 많은 JSON 모듈들은 use overload를 사용해 연산자 오버로딩으로 이 문제를 해결하고 있습니다. 따라서 다음과 같은 표기법을 권장합니다.

use JSON;

# {"is_jeen_drunken":true}
print JSON->new->encode({ is_jeen_drunken => JSON->true });

print JSON->true;      # 1
print ref(JSON->true); # JSON::PP::Boolean
if (JSON->false) { print "No~" }  # No~ 라고 표시안됨

대개의 불리언형의 데이터를 반환하는 메서드에서 true라면 JSON->true라고 반환할까요?

package Person;
use JSON;

sub is_drunken { JSON->true }

...

왠지 이러면 모든 불리언형의 데이터가 JSON과 깊은 관계가 있다고 오해할 여지도 있어 다른 방법을 알아보기로 했습니다. 단지 네이밍의 문제라 이거죠.

다른 대체제는 무엇이 있을까요?

use boolean

아시다시피 펄은 그저 CPAN을 사용하기 위한 도구 CPAN을 사용해서 여러 위기상황을 헤쳐나갑니다. 다음은 boolean 모듈을 사용해 true, false를 사용하는 예제입니다.

use boolean;

my $is_jeen_drunken    = true;
my $is_jeen_homophobe  = false;

print true;   # 1
print false;  # 2

if (true)  { print "True"  } # True
if (false) { print "False" }

-truth 옵션을 지정하면 ref()로 참조값까지 얻을 수 있습니다.

use boolean -truth;

print ref(0 == 1); # boolean
print ref(true);   # boolean

위의 JSON->true 같은 형태보다는 훨씬 더 깔끔합니다. 하지만 JSON 출력시에는 boolean도 좀 까탈스러운 과정을 거쳐야 합니다. 일반적인 JSON, JSON::XSconvert_blessed 옵션이 이 boolean에는 먹히지 않습니다(null을 반환합니다). 문서에서는 JSON::MaybeXS를 사용하는 것을 권장합니다. 분명 convert_blessed 옵션으로는 TO_JSON을 호출할텐데 왜 안되는지에 대해서는 나중에 따로 알아보겠습니다.

use Types::Serialiser

다른 방법도 있습니다. 바로 Types::Serialiser 모듈를 사용하는 것이죠.

use JSON;
use Types::Serialiser;  # JSON::XS가 설치되어 있는 경우
                        # `import`에서 처리되므로 use할 필요는 없음

print JSON->new->encode({ is_jeen_drunken => Types::Serialiser::true });

Boolean 형이 단지 JSON 만을 위한 것이 아니라는 것을 어필하는 것 같은 이름이지 않습니까? 사실 JSON::XS을 만든 Marc Lehman이 그렇게 생각했던 것같습니다. JSON뿐만이 아니라 CBOR 등에서도 사용할 수 있습니다.

그리고 조금 손이 가지만 MessagePack도 사용할 수 있습니다. Data::MessagePack 모듈의 경우도 불리언형을 지원합니다. 다음은 Types::Serialiser로 정의한 true/false를 잘 맞출 수 있도록 관련 부분을 사전에 변환하는 예제입니다.

use Data::MessagePack;
use JSON::XS;

BEGIN {
    *Data::MessagePack::Boolean:: = *Types::Serialiser::Boolean::;
    *Data::MessagePack::true      = *Types::Serialiser::true;
    *Data::MessagePack::false     = *Types::Serialiser::false;
}

my $mp = Data::MessagePack->new;

my $data = {
    aaa => Types::Serialiser::true,
    bbb => Types::Serialiser::false,
};
my $r = $mp->pack($data);
print $r;         # ��aaaãbbb�
print length($r); # 11

my $u = $mp->unpack($r);
my $json = JSON::XS->new->encode($u);
print $json;         # {"bbb":false,"aaa":true}
print length($json); # 24

정리하며

웹 개발에서 JSON으로 데이터를 주고받는 것은 거의 상식처럼 받아들여지고 있습니다. 하지만 사실 1바이트로 표현될 Boolean에 4~5바이트의 문자열이 오가는 상황이고... Javascript만 좋은 일 시키고 있습니다.

어쩌다보니 불리언으로 시작했는데, 마지막은 데이터 직렬화 이야기가 되어버렸네요. 앞에서도 언급한 대로 이 모든 것은 소 잃고 외양간 고치기로 시작한 것입니다. 현재 진행중인 프로젝트에서 무턱대고 API의 응답결과를 저렇게 바꿔버리면 제품은 무너지고 저는 깨질테니까요.

지금까지는 불리언 불가론을 깨기 위한 방법소개에 지나지 않았습니다. 다음 기사에서는 이런 기법을 이용해 펄의 객체지향 프로그래밍에서 어떻게 적용할 수 있을지 살펴보겠습니다. :)

blog comments powered by Disqus