jupyter notebook에 scala 커널 올려보기

어제는 자바를 쥬피터에 붙여보았는데,
브루스 테이트의 세븐랭귀지를 공부하는 중이고
마침 공부해야하는 챕터가 스칼라라서 스칼라도 붙여보기로 했다.

찾아보니 스칼라는 제플린이나 암모나이트 같은 녀석들도 있었는데,
여러가지 툴을 사용하는 것도 귀찮고, 쥬피터 노트북으로도 공부는 충분히 하고도 남을 것 같아서
쥬피터 노트북에 설치해보기로 했다.

다운로드 및 설치

➜ git clone https://github.com/alexarchambault/jupyter-scala.git
➜ cd jupyter-scala
➜ sbt publishLocal

Getting org.scala-sbt sbt 0.13.15 (this may take some time)…
downloading https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt/0.13.15/jars/sbt.jar …
[SUCCESSFUL ] org.scala-sbt#sbt;0.13.15!sbt.jar (7448ms)
downloading https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.10.6/scala-library-2.10.6.jar …
[SUCCESSFUL ] org.scala-lang#scala-library;2.10.6!scala-library.jar (30749ms)
downloading https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/main/0.13.15/jars/main.jar …
[SUCCESSFUL ] org.scala-sbt#main;0.13.15!main.jar (17395ms)

….한…참걸림 and 중략
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/poms/scala-cli_2.12.2.pom
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/jars/scala-cli_2.12.2.jar
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/srcs/scala-cli_2.12.2-sources.jar
[info] published scala-cli_2.12.2 to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/docs/scala-cli_2.12.2-javadoc.jar
[info] published ivy to /Users/andy/.ivy2/local/org.jupyter-scala/scala-cli_2.12.2/0.4.3-SNAPSHOT/ivys/ivy.xml
[success] Total time: 160 s, completed Apr 20, 2018 10:36:21 PM

jupyter에서 사용할 scala 설치

➜ ./jupyter-scala –id scala-develop –name “Scala (develop)” –force

이것도 한참 걸림…

잘 되는지 확인

➜ jupyter kernelspec list

➜ jupyter kernelspec list
Available kernels:
scala-develop /Users/andy/Library/Jupyter/kernels/scala-develop
python3 /Users/andy/.pyenv/versions/py3/share/jupyter/kernels/python3

위와 같이 scala가 있으면 성공임

아래는 연습으로 끄적여 본 스크립트입니다~

jupyter notebook에 자바 커널 올려보기

1. java jdk 설치

아래 링크에서 받아서 설치
http://www.oracle.com/technetwork/java/javase/downloads/index.html

버전 확인 (jdk 9 버전 이상이 필요하다함)

<code>➜ ~ java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
</code>

2. 자바 커널 설치

<code>➜ git clone https://github.com/SpencerPark/IJava.git --depth 1
➜ ~ chmod +x gradlew
✗ ./gradlew installKernel
... 중략
BUILD SUCCESSFUL in 42s
</code>

쥬피터 커널 확인하기

<code>➜ jupyter kernelspec list
Available kernels:
java /Users/gyus/.ipython/kernels/java
python3 /usr/local/opt/pyenv/versions/3.6.0/share/jupyter/kernels/python3
</code>

아래와 같이 테스트 해보았다.

golang의 sort패키지 사용해보기

[markdown]
golang에 sort라는 패키지가 있을 것이다라는 생각은 했지만, 잘 되어 있을거라는 생각은 못했는데,왠걸 내가 구현한것 보다 훨씬 잘 되어 있어서 내가만든거는 버리기로 했다.
내가 원하는 기능의 리스트는 아래와 같았다.

– 구조체 안에 있는 점수를 비교해서 구조체 리스트를 정렬하고 싶다.
– 점수가 같은 경우에는 나중에 업데이트된 녀석에게 상위 등급을 주고 싶다.
– 역순으로 정렬 하면 좋겠다.

결론적으로는 내가 원하는 기능보다 강력한 기능이 go의 sort패키지에 탑재 되어 있었다.
일단 정렬을 하려면 인터페이스를 만족시켜줘야 하는데, go의 인터페이스의 구현은 직접적으로 구현을 하는 것이 아니라,
그냥 해당 인터페이스가 제공하는 메서드를 모두 가지고 있으면, 그 인터페이스를 구현한것이다 라고 하기 때문에 sort패키지의 인터페이스를 단순히 구현을 하면 된다.
int, float, string은 go를 만드신 분들이 만들어 놨기 때문에, 그냥 쓸 수 있다. 그리고 내가 만든 구조체를 정렬하기 위해서는 구조체를 정렬하기 위한 타입을 만들어서 인터페이스의 메서드들을 구현해 주면 된다~!
말로하니깐 긴데 코드는 그렇게 길지 않다.

“`
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
“`

sort인터페이스코드인데 Len, Less, Swap 세가지 메서드를 구현하면 sort.Interface가 되는 것이되겠다.

### 숫자 정렬

일단 그냥 숫자를 정렬해보자.
import 관련된 코드는 그냥 생략 하도록 하겠다.

“`
func sortInt() {
// 램덤한 수를 세팅하기 위해 유닉스 시간으로 Seed를 저장
// 리스트의 숫자는 30개로 지정한다.
rand.Seed(time.Now().UTC().UnixNano())
count := 30
maxInt := 10000
arr := make([]int , count)

for i, _ := range arr {
arr[i] = rand.Int() % int(maxInt)
}

fmt.Println(“Sort this array : “, arr)
sort.Sort(sort.IntSlice(arr))
fmt.Println(“Sorted result : “, arr)
sort.Sort(sort.Reverse(sort.IntSlice(arr)))
fmt.Println(“Reversed result : “, arr)
}
“`

실행하면 이런 식으로 나올 것이다.

“`
Sort this array : [8298 6378 8489 5548 734 6301 6347 2620 2636 2569 3039 168 2130 8599 9316 723 4642 6531 147 42 188 264 5544 260 5101 5595 550 6594 9128 3430]
Sorted result : [42 147 168 188 260 264 550 723 734 2130 2569 2620 2636 3039 3430 4642 5101 5544 5548 5595 6301 6347 6378 6531 6594 8298 8489 8599 9128 9316]
Reversed result : [9316 9128 8599 8489 8298 6594 6531 6378 6347 6301 5595 5548 5544 5101 4642 3430 3039 2636 2620 2569 2130 734 723 550 264 260 188 168 147 42]
“`

### 구조체 정렬

숫자는 해봤으니, 이제 좀 더 실제적으로 사용할 만한 녀석으로 구조체를 정렬 해보자. 내가 하고자 하는것은 카카오 게임에서 랭킹을 만들어주는 역활을 하는 녀석이다.
구조체는 이런식으로 생겼다. 더 많은 데이터가 있지만, 공부에 필요없는 녀석은 생략했다.

“`
type Ranking struct {
kakaoId string
score int
written_date int // unix timestamp
}

// 테스트 결과를 이뿌게 보기위해 String 함수를 추가하였다.
func (ranking Ranking) String () string {
return fmt.Sprintf(“kakaoid %s score %d written_date %d\n”, ranking.kakaoid, ranking.score, ranking.written_date)
}
“`

그럼 이제부터 Ranking 구조체를 위한 예제 코드를 만들어보자.
아래 코드는 sort.interface를 구현 하기 위한 코드들이다. 소팅에 사용할 RankingSlice 타입을 새로 정의 하였다.
그리고 `Len, Less, Swap` 세개의 함수를 만들어 주었다.
점수가 큰녀석이 상위 랭크가 되도록 하였고, 점수가 같은 경우는 written_date가 큰녀석이 상위랭크가 되도록 작성했다.

“`
type RankingSlice []Ranking
func (arr RankingSlice) Len() int {
return len(arr)
}

// j 인덱스를 가진녀석이 i 앞으로 와야하는지 말아야하는지를 판단하는 함수
func (arr RankingSlice) Less(i, j int) bool {
if arr[i].score == arr[j].score {
return arr[i].written_date > arr[j].written_date
} else {
return arr[i].score > arr[j].score
}
}

func (arr RankingSlice) Swap(i, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
“`

소팅을 하기위한 준비는 이제 끝났다.
테스트 코드를 만들고 테스트 해보자.

“`
func sortRankings(){
rankings := []Ranking {
{“88154420822432544”, 1436520192, 13960},
{“91670176688136577”, 1440799941, 13080},
{“88180751521592112”, 1435637878, 14280},
{“88144324860977520”, 1437278775, 13560},
{“93797540915256451”, 1435889873, 14200},
{“88249875880704624”, 1438226613, 11960},
{“88192601275706464”, 1436694190, 13320},
{“88148115536237952”, 1436450939, 13600},
{“89047353485705664”, 1435654249, 12760},
{“88182263314045056”, 1436586635, 13320},
{“88193534078563601”, 1438176365, 16600},
{“88256711722920353”, 1437136853, 8920},
{“89852877819675984”, 1436684270, 12680},
{“93594773313085234”, 1437634370, 8480},
{“90289273133727025”, 1436351481, 10800},
{“92150085612088915”, 1440609977, 12720},
{“91211930152797040”, 1438076721, 10920},
{“88309448624594448”, 1438057506, 10440},
{“88406950494717233”, 1436508922, 11000},
{“93176928270204371”, 1437293064, 10440},
{“88056851903088048”, 1436338570, 10440},
{“93691000129391522”, 1436618165, 12560},
{“88675559501659840”, 1438230885, 9800},
}

sort.Sort(RankingSlice(rankings))
fmt.Println(rankings)
}
“`

크게 한것 없이 정렬이 끝났다. 아래와 같은 결과가 나올 것이다.

“`
kakaoid 88193534078563601 score 16600 written_date 1438176365
kakaoid 88180751521592112 score 14280 written_date 1435637878
kakaoid 93797540915256451 score 14200 written_date 1435889873
kakaoid 88154420822432544 score 13960 written_date 1436520192
kakaoid 88148115536237952 score 13600 written_date 1436450939
kakaoid 88144324860977520 score 13560 written_date 1437278775
kakaoid 88192601275706464 score 13320 written_date 1436694190
kakaoid 88182263314045056 score 13320 written_date 1436586635
kakaoid 91670176688136577 score 13080 written_date 1440799941
kakaoid 89047353485705664 score 12760 written_date 1435654249
kakaoid 92150085612088915 score 12720 written_date 1440609977
kakaoid 89852877819675984 score 12680 written_date 1436684270
kakaoid 93691000129391522 score 12560 written_date 1436618165
kakaoid 88249875880704624 score 11960 written_date 1438226613
kakaoid 88406950494717233 score 11000 written_date 1436508922
kakaoid 91211930152797040 score 10920 written_date 1438076721
kakaoid 90289273133727025 score 10800 written_date 1436351481
kakaoid 88309448624594448 score 10440 written_date 1438057506
kakaoid 93176928270204371 score 10440 written_date 1437293064
kakaoid 88056851903088048 score 10440 written_date 1436338570
kakaoid 88675559501659840 score 9800 written_date 1438230885
kakaoid 88256711722920353 score 8920 written_date 1437136853
kakaoid 93594773313085234 score 8480 written_date 1437634370
“`

처음에는 퀵소트를 구현해서 정렬하려고 퀵소트를 만들기 까지 했지만, 너무 잘 만들어놓은 패키지 때문에 큰 노력없이 좋은 결과를 얻을 수 있었다.
위에서 테스트로 만든 것 이외에도 소팅할 키를 지정하기, 멀티키로 소팅하기 같은 기능도 sort패키지를 사용하면 손쉽게(?!) 구현할 수 있다.
관련 정보는 golang.org의 sort 패키지를 확인해 보자.

[sort패키지](https://golang.org/pkg/sort/)

전체 소스코드는 아래의 링크에 있다.

[소스코드보기](https://gist.github.com/wapj/5b75ad16d32727eed61036fdb6c349e7)

[/markdown]

[install] powerdns 설치하기

[markdown]
## Private DNS? POWERDNS!

DB를 마스터/슬레이브로 운영하고 있을 경우, 마스터 디비가 죽으면 슬레이브 디비를 마스터로 승격시켜주는 녀석들이 있다.(MMM, MHA등등) 그런데, 보통 디비를 바라보고 있는 클라이언트는 보통 아이피나 도메인을 통해서 디비를 바라보고 있을 것이다. 그런 상황에서 슬레이브가 마스터로 승격되어 봤자 클라이언트들은 죽어있는 마스터를 바라보게 마련이다.

이런경우 슬레이브를 마스터로 승격 시켜주는 것과 별개로 아이피나 도메인도 같이 바꿔줘야 하는데,
아이피를 바꾸려면 가상아이피라는 녀석을 써야한다. 그런데 지금 사용하는 클라우드 서비스에서는 가상아이피를 지원하지 않는관계로,어쩔 수 없이(?!) 도메인을 써야하는 상황이었다.

어쨌든 클라이언트들이 모르게 도메인만 슬쩍 바꿔버리면, 클라이언트는 마스터가 죽었는지 살았는지 모를테니, dns서버라는 녀석을 도입하기로 했다. 그중에 제일 간편해 보이는 녀석이 powerdns 였는데..(설치는 간편하지 않다) web api도 지원하고 해서 실제 서비스에 도입해서 사용하고 있다.

Private DNS라고 쓰긴했지만, 내가 그렇게 쓴다는거지, Public DNS 서버로 못쓴다는 얘기는 아니다.

궁금한 사람은 [powerdns](https://doc.powerdns.com) 문서 페이지와 [github](https://github.com/PowerDNS/pdns)에서 더 많은 정보를 볼 수 있을 것이다.

### MariaDB설치
PowerDNS는 벡엔드로 Mysql을 사용할 수 있는데, 나는 MariaDB를 사용하기로 했다. (이유가 있었던것 같은데, 까먹었다.)
여튼 설치를 해보자. root 유저로 들어가서 실행해야한다.

“`
# apt-get update && apt-get upgrade -y
# apt-get install software-properties-common
# apt-key adv –recv-keys –keyserver hkp://keyserver.ubuntu.com:80 0xcbcb082a1bb943db
# add-apt-repository ‘deb http://ftp.kaist.ac.kr/mariadb/repo/5.5/ubuntu trusty main’
# apt-get -y install libaio1 libdbd-mysql-perl libdbi-perl libmariadbclient18 libmysqlclient18 libnet-daemon-perl libplrpc-perl mariadb-client-5.5 mariadb-client-core-5.5 mariadb-common mysql-common mariadb-server mariadb-server-5.5 mariadb-server-core-5.5
“`

### mariadb 보안 적용
보안을 위해서 하는 건데, 테스트환경이라면 굳이 하지 않아도 된다.

“`
$ mysql_secure_installation
“`

위의 코드를 실행하면 아래와 같이 물어보는데, 읽어보고 yes를 해주면 된다.

“`
Change the root password? [Y/n] n
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] y
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y
Thanks for using MariaDB!
“`

### powerdns 유저 생성및 데이터베이스와 테이블 생성

powerdns_init.sql이라는 파일을 하나 생성하고 아래의 내용을 채워넣자. (*패스워드는 알아서 잘 바꿔주자. 복붙금지!*)

“`
CREATE DATABASE powerdns;
GRANT ALL ON powerdns.* TO ‘pdns’@’localhost’ IDENTIFIED BY ‘password’;
FLUSH PRIVILEGES;
USE powerdns;
CREATE TABLE domains (
id INT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
notified_serial INT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX name_index ON domains(name);
CREATE TABLE records (
id INT AUTO_INCREMENT,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(64000) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
change_date INT DEFAULT NULL,
disabled TINYINT(1) DEFAULT 0,
ordername VARCHAR(255) BINARY DEFAULT NULL,
auth TINYINT(1) DEFAULT 1,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername);

CREATE TABLE supermasters (
ip VARCHAR(64) NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY (ip, nameserver)
) Engine=InnoDB;
CREATE TABLE comments (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) NOT NULL,
comment VARCHAR(64000) NOT NULL,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
CREATE TABLE domainmetadata (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
kind VARCHAR(32),
content TEXT,
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);
CREATE TABLE cryptokeys (
id INT AUTO_INCREMENT,
domain_id INT NOT NULL,
flags INT NOT NULL,
active BOOL,
content TEXT,
PRIMARY KEY(id)
) Engine=InnoDB;

CREATE INDEX domainidindex ON cryptokeys(domain_id);
CREATE TABLE tsigkeys (
id INT AUTO_INCREMENT,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255),
PRIMARY KEY (id)
) Engine=InnoDB;

CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
“`

디비생성후에 다음의 커맨드로 mariadb에 반영해주자.

“`
$ mysql -u root -p < powerdns_init.sql ``` ### Powerdns설치 (4.0.X) /etc/apt/source.list에 아래한줄을 추가한다. ``` # add-apt-repository 'deb [arch=amd64] http://repo.powerdns.com/ubuntu trusty-auth-40 main' ``` 그리고 `/etc/apt/preferences.d/pdns` 파일을 생성하고 아래 내용을 추가한다. ``` # touch /etc/apt/preferences.d/pdns # echo -e "Package: pdns-*\nPin: origin repo.powerdns.com\nPin-Priority: 600" | sudo tee /etc/apt/preferences.d/pdns ``` 위의 것들을 하면 아래의 명령어로 실행할 수 있다. ``` # curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add - # apt-get update && apt-get upgrade # apt-get install -y pdns-server pdns-backend-mysql # rm /etc/powerdns/pdns.d/* ### 기존의 설정 삭제 꼭 해줘야한다. ``` 설치시에 아래와 같은 에러가 난다면, 아래의 명령을 실행해주자. `# apt-get -f purge -y mysql-client` ``` You might want to run 'apt-get -f install' to correct these. The following packages have unmet dependencies: mariadb-server : Depends: mariadb-server-5.5 (= 5.5.49+maria-1~trusty) but 5.5.49-1ubuntu0.14.04.1 is installed E: Unmet dependencies. Try using -f. ``` 설치시 아래와 같은 화면이 나오는데, No를 선택해주자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-1-1.png) 그리고 /etc/powerdns/pdns.d/pdns.local.gmysql.conf 파일을 아래와같이 수정해 주어야 한다. 디비의 유저와 패스워드를 잘 바꿔서 넣어주자. ``` ### MySQL Configuration file launch=gmysql gmysql-host=localhost gmysql-dbname=디비명 gmysql-user=유저명 gmysql-password=

###수정후 mariadb를 재시작해준다.
$ service pdns restart
“`

### pdns가 잘동작하는지 테스트
netstat와 dig를 사용했을 때 아래와 비슷하게 나와야 한다.

“`
$ netstat -nlp |grep pdns
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN 6793/pdns_server-in
udp 0 0 0.0.0.0:53 0.0.0.0:* 6793/pdns_server-in
unix 2 [ ACC ] STREAM LISTENING 46518 6789/pdns_server /var/run/pdns.controlsocket
$ dig @localhost
; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> @localhost
; (3 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28331 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 2800 ;; QUESTION SECTION: ;. IN NS ;; Query time: 2 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Mar 10 20:06:16 KST 2016 ;; MSG SIZE rcvd: 29 ``` ### Poweradmin 설치 powerdns에는 Poweradmin 이라는 php관리 페이지가 따로 있다. 설치가 어렵지않고, 설치하면 많은 기능을 웹기반으로 쉽게사용할 수 있으니 설치하도록 하자. (커맨드라인으로 굳이하겠다면 말리지는 않음) ``` $ apt-get install -y unzip apache2 gettext libapache2-mod-php5 php5 php5-common php5-curl php5-dev php5-gd php-pear php5-imap php5-ming php5-mysql php5-xmlrpc php5-mhash php5-mcrypt $ pear upgrade pear/PEAR $ pear install DB $ pear install pear/MDB2#mysql ### php 모듈 활성화 $ php5enmod mcrypt $ service apache2 restart $ cd tmp $ wget https://github.com/poweradmin/poweradmin/archive/master.zip -O poweradmin.zip $ unzip poweradmin.zip $ mv poweradmin-master /var/www/html/poweradmin ``` ###Poweradmin 설정 설치가 끝났으니 아래의 주소로 들어가서 설정을 하도록하자. http://#{server_ip}/poweradmin/install/ [![](http://gyus.me/wp-content/uploads/2016/05/pdns2-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns2-1-1.png) 들어가면 위와 같은 언어 선택 화면이 나온다. 대부분 영어겠지만, 익숙한 언어를 선택하고 Go to step2로 가자. 나는 영어를 선택했다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns3-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns3-1-1.png) 스텝2는 별거 없다. 바로 Go to step3 를 누르자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns4-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns4-1-1.png) Step3에서는 DB와 Poweradmin에 대한 설정을 하게 된다. 적절한 값을 넣도록 하자. ``` Username : DB와 연결할 유저명을 적으면 된다. 우리는 위에서 pdns라는 유저를 생성했다. Password : 디비패스워드 Database type : MySQL Hostname : localhost DB Port : 3306 Database : powerdns Poweradmin administrator password : 파워애드민에서 사용할 관리자 패스워드 ``` [![](http://gyus.me/wp-content/uploads/2016/05/pdns5-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns5-1-1.png) Username : poweradmin의 관리자명 Password: poweradmin의 관리자 패스워드 hostmaster.unixmen.local – SOA레코드를 만들때 없을경우 기본값으로 들어가는 값. 다음과 같은 방식이 된다. “hostmaster.example.net”. ns1.unixmen.local – 템플릿을 만들때 주 네임서버로 설정되는 값. 요런식으로 적는다. “ns1.example.net”. ns2.unixmen.local – 템플릿을 만들때 주 네임서버로 설정되는 값. 요런식으로 적는다. “ns2.example.net”. 잘 적은뒤에 Step5로 가자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns6-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns6-1-1.png) Step5에서는 poweradmin에서 powerdns의 유저가 디비에 접근할때의 권한을 SELECT, INSERT, UPDATE, DELETE 만주라고 권장하고 있다. 화면에 있는 내용을 mysql에서 실행해주면 된다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns7-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns7-1-1.png) Step6에서는 poweradmin의 설정파일을 "../inc/config.inc.php" 경로에 만들어라고 말을하고 있다. 해당 내용을 복사해서 만들어주자. 설정파일에 db_user가 admin 이라고 되어있는데, 디비에 유저가 생성되지 않은 경우가 있으므로 그경우에는 기존의 'pdns'유저로 해당 파일의 설정을 변경해주자. 나 같은 경우는 "/var/www/html/poweradmin/inc/config.inc.php" 경로에 생성을 했다. 7단계로 가자. 이게 설치는 거의 다되어 간다! [![](http://gyus.me/wp-content/uploads/2016/05/pdns8-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns8-1-1.png) 7단계에서는 DDNS를 사용하고 싶으면 mod_rewrite를 활성화 하라고 하고 있다. 그리고 반드시 "install" 디렉토리를 삭제하라고 한다. 다음으로 poweradmin의 "install" 디렉토리를 반.드.시. 삭제해야한다. (나는 사실 이름만 바꿨다. 실서버에서는 이렇게 하면 안된다.) [![](http://gyus.me/wp-content/uploads/2016/05/pdns9-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns9-1-1.png) http://server_ip/poweradmin 으로 들어왔을 때 위와 같은 화면이 나오면 설치는 성공이다. Username과 Password에 위에서 설정한 값을 넣으면 Poweradmin에 접근이 가능하다. 이제 설치는 끝이다. ### DNS서버 설정 [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-10-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-10-1-1.png) Add Master Zone을 추가하자. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-11-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-11-1-1.png) Zone name에 'pkpk.com' 등과 같은 사용하고 싶은 주도메인을 입력하고 Add zone 을 눌러서 추가해준다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-12-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-12-1-1.png) List zones에 들어가면 위와같은 화면이 나오고 빨간 동그라미친 곳에 들어가면 A record등을 추가할 수 있다. [![](http://gyus.me/wp-content/uploads/2016/05/pdns1-13-1-1.png)](http://gyus.me/wp-content/uploads/2016/05/pdns1-13-1-1.png) `alpha.pkpk.com` 이라는 도메인을 요청하면 `192.168.0.8`로 응답을 주도록 세팅을 했다. ### DNS서버 테스트 이제 잘되는지 테스트를 해보자. 먼저는 powerdns가 설치된 서버에서 실행을 해보자. 다음과 같은 코드를 실행해보자 `$ dig alpha.pkpk.com A @127.0.0.1` 아래와 비슷한 결과가 나오면 성공이다. ``` ; <<>> DiG 9.9.5-3ubuntu0.8-Ubuntu <<>> alpha.pkpk.com A @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18817 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 2800 ;; QUESTION SECTION: ;alpha.pkpk.com. IN A ;; ANSWER SECTION: alpha.pkpk.com. 5 IN A 192.168.0.8 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Thu Mar 10 21:21:09 KST 2016 ;; MSG SIZE rcvd: 59 ``` 이제 다른 서버에 DNS서버를 연결해서 테스트해보자. ``` ### ubuntu14.04에서 실행했다. root권한으로 실행해야한다. $ echo "nameserver dnsserver의 아이피" >> /etc/resolvconf/resolv.conf.d/head
$ service networking restart
$ sudo resolvconf -u
“`

설정이 끝났으니 위에서 추가한 alpha.pkpk.com 으로 핑을 날려보자.

“`
$ ping alpha.pkpk.com
PING alpha.pkpk.com (192.168.0.8) 56(84) bytes of data.
64 bytes from 192.168.0.8: icmp_seq=1 ttl=64 time=0.398 ms
64 bytes from 192.168.0.8: icmp_seq=2 ttl=64 time=0.279 ms
64 bytes from 192.168.0.8: icmp_seq=3 ttl=64 time=0.282 ms
64 bytes from 192.168.0.8: icmp_seq=4 ttl=64 time=0.303 ms

— alpha.pkpk.com ping statistics —
4 packets transmitted, 4 received, 0% packet loss, time 3001ms
rtt min/avg/max/mdev = 0.279/0.315/0.398/0.051 ms
“`

위와 같이 핑테스트가 잘되면 성공이다.

### API

powerdns의 3.4버전부터 api를 사용가능하다.
우분투에서 `apt-get install`로 설치하는 녀석들은 버전이 낮은 경우가 많으니 주의하자.
설정파일에 아래와 같은 부분을 설정하고, powerdns를 재시작 해주면 활성화가 된다.

“`
설정을 업데이트하는 API를 사용하려면, mysql의 설정의 binlog_format을 row로 변경해주어야 한다.
“`

“`
api=yes
api-key=changeme
webserver=yes
“`

### API 사용예제

<`주의`> API를 사용해서 만든 zone 정보만 API를 사용해서 조작할 수 있다. 이점에 유의해야한다.

“`
# zone list
$ curl -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones | jq .

# create zone
$ curl -X POST –data ‘{“name”:”example.org.”, “kind”: “Native”, “masters”: [], “nameservers”: [“ns1.example.org.”, “ns2.example.org.”]}’ -v -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones | jq .

# show zone info
$ curl -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones/pkpk.com | jq .

# change sub domain info
$curl -X PATCH –data ‘{“rrsets”: [ {“name”: “gamedb1.pkpk.com”, “type”: “A”, “ttl”: 0, “changetype”: “REPLACE”, “records”: [ {“content”: “192.168.0.29”, “disabled”: false } ] } ] }’ -H ‘X-API-Key: changeme’ http://192.168.0.44:8081/api/v1/servers/localhost/zones/pkpk.com | jq .

“`
[/markdown]

[알고리즘] 소수 구하기

요즘 코딩 감각이 좀 둔해진 것 같아서, 다시 프로젝트 오일러 정주행중인데, 예전에 처음 풀었을 때에는 그냥 뺑뺑이 돌려서 소수를 구했었다.

이번에는 두번째 하는거라서 자료를 조금 찾아보기도 하고, 나름 생각해서 이렇게 하면 빠르겠다
싶은 것도 만들어보기도 하였다…

결과적으로는 수학을 이용한 것이 제일 빨랐다..

첫번째 메서드 실행결과

elapsed time : 0:03:00.578463
method1 DONE!

두번째 메서드 실행결과

elapsed time : 0:00:00.272193
method2 DONE!

세번째 메서드 실행결과

elapsed time :  0:00:00.305218
method3 DONE!

아래는 작성해본 코드이다.

[gfm]
“`
from datetime import datetime

def timer(method):
def timed(*args, **kwargs):
start = datetime.now()
result = method(*args, **kwargs)
end = datetime.now()

print (“elapsed time : “, end – start)
return result

return timed

### 정의대로 만든거
def isPrime(x):
if x == 1:
return False
else:
for num in list(range(2,x)):
if x % num == 0:
return False
return True

### 약간의 수학을 이용
### x 가 a와 b의 곱으로 이루어진다면 a와 b의 최대값은 x**0.5 보다 작거나 같아야 한다. 는 원리를 이용
def isPrime2(x):
if x == 2 or x == 3: return True
if x%2 == 0 or x < 2 : return False for i in range(3, int(x**0.5) + 1, 2): if x%i == 0: return False return True ### 인터넷에서 본거 def isPrime3(n): if n < 2: return False if n % 2 == 0: return n == 2 k = 3 while k * k <= n: if n % k == 0: return False k += 2 return True @timer def execute1(size=1000): prime_list = [] for x in list(range(2, size)): if isPrime(x): prime_list.append(x) print("method1 DONE!") print(prime_list) @timer def execute2(size=1000): prime_list = [] for x in list(range(2, size)): if isPrime2(x): prime_list.append(x) print("method2 DONE!") print(prime_list) @timer def execute3(size=1000): prime_list = [] for x in list(range(2, size)): if isPrime3(x): prime_list.append(x) print("method3 DONE!") print(prime_list) if __name__ == '__main__': num = 100000 execute1(num) execute2(num) execute3(num) pass ``` [/gfm]

파이썬 가상 환경 설정 및 장고 설치하기

[markdown]
갑자기 회사의 동료가 나에게 장고스터디를 해달라고 했다. 마침 장고를 보고있었던 나는 쥐뿔도 모르지만, 이제부터 준비하려고 장고를 열심히 보고 있다.  근데, 나쁜버릇이 나와서 설치부터 잘하고 싶은 마음이 드는 것이다.

남들은 그냥 `sudo pip install django` 이렇게 하고 넘어갔을 것을 괜시리 깔끔떨며 가상환경이라는 것을 만들어보기로 한다. 그럼 같이 한삽을 같이 퍼보도록 하자.

아래에서 실행한 스크립트들은 **virtualbox에 새로 설치한 ubuntu14.04 LTS**에서 아주 깔끔한 상태로 실행한 것들이다.  다른 Linux에서는 설치하는게 비슷하거나 약간 다를것이고, windows에서는 아~~주 많이 다를것이다. 그러므로 그점을 인지하고 보도록 하자.

사실 OSX는 예전에 정리해놓은 글이 있다. 똑같은걸 두번이나..적다니..ㅠㅠ

[맥에서 쟝고개발환경 만들기](http://gyus.me/?p=240)

###우분투에서 python3 설치
`sudo apt-get install python3-all`

###pip 설치
`sudo apt-get install python-pip `

###virtualenv 설치
virtualenv를 설치하는 이유는 여러가지가 있지만, 내가 아는 바로는 아래 4가지다.

– 파이썬 패키지를 설치할 때 관리자 권한이 아니라 유저 권한으로 설치 하고 싶음.
– 파이썬의 실행 환경을 리눅스의 것과 온전히 구분해서 서버를 깔끔하게 하고 싶음.
– python3을 실행할 때 그냥 python으로 실행하고 싶음.
– 개발환경과 실서버환경을 동일하게 맞추고 싶음.

그럼 설치해보자. (간단간단)

`sudo pip install virtualenv`

###virtualenvwrapper 설치
뭘 이런 헷갈리는걸 또 설치하냐고 물어볼 사람이 있을 것 같은데(나도 그랬음), 그냥 세트라고 생각하고 설치하는게 좋다. virtualenv를 훨씬 사용하기 간편하게 해준다.

`sudo pip install virtualenvwrapper`

###virtualenv 환경 설정
1. 디폴트 디렉토리 만들기 .virtualenv
mkdir ~/.virtualenvs
2. WORKON_HOME 환경 변수에 디폴트 디렉토리 등록하기
export WORKON_HOME=~/.virtualenvs
3. .profile(우분투 기준 유저 로그인시 실행되는 스크립트) 에 virtualenvwrapper를 임포트 하도록
위 파일의 가장 하단에 아래 스크립트 추가

`. /usr/local/bin/virtualenvwrapper.sh`
혹은
`source /usr/local/bin/virtualenvwrapper.sh`

###가상환경 만들어보기

위의 과정을 잘 따라했다면 `virtualenvwrapper.sh` 내에 있는 함수인 `mkvirtualenv` 함수를 사용할 수 있다.

`mkvirtualenv {{가상환경-이름}}`
요렇게 사용한다

`mkvirtualenv py2`

요렇게하면 py2라는 이름의 가상환경이 설치되고 그 가상환경을 사용할 수 있게(workon) 된다.

`which python`
명령어를 실행했을 때 아래와 같은 경로로 나오면 제대로 된것이다.
`/home/user_name/.virtualenvs/py2/bin/python`

가상환경에서 나가려면
`$ deactivate`
와 같이 실행한다

다시 들어가려면
`$ workon py2`
라고 하면된다.

###그럼 python3의 가상환경을 만들고 싶으면?

`virtualenv`가 인식하는 환경변수중에 `VIRTUALENV_PYTHON` 이라는 환경변수가 있는데, 저 변수에 `python3`의 경로를 넣어 주면 된다.

이렇게 해보자.
“`
export VIRTUALENV_PYTHON=`which python3`
mkvirtualenv py3
“`

이렇게 하면 환경변수 안쓰고도 된다!(이진석님 감사합니다.)
“`
mkvirtualenv py3 –python=`which python3`
“`
물론 위의 `which python3`이 제대로 되려면 `python3`이 설치가 되어있어야 한다.
(`sudo apt-get install python3-all`을 위에서 실행한 이유가 그것 때문)

이렇게 하면 파이썬3의 환경으로 가상환경이 만들어졌다.
파이썬 버젼을 확인해보자.
`(py3) ~ $ python -V `
`Python 3.4.0`

요런식으로 나오면 성공~!
긜고 pip3도 pip 로 실행된다.

####아..이제 환경설정이 됐으니 장고를 설치할 수 있다…;;;

### 장고설치
쟝고의 설치는 허무하게도 아래의 한줄

`pip install django`

장고 설치끝~
[/markdown]

boto의 profile_name 설정으로 여러개의 Credential 사용하기

[markdown]
# boto의 profile_name 설정으로 여러개의 Credential 사용하기

### 주의
AWS_CREDENTIAL_FILE 이 환경변수로 있는 경우에는 자동으로 해당 파일을 `boto`에서 인식하므로 여러개의 credential 설정을 사용할 수 없다. 이 경우에는 해당 설정을 삭제해야한다.

`boto`의 환경설정 파일로 인식되는 경로로 아래의 3가지가 있다.

* /etc/.boto : 모든 유저가 공유하는 세팅
* ~/.boto : 각 유저별 세팅
* ~/.aws/credentials : aws SDK와 공유하는 설정

우리가 만저볼 것은 `~/.boto` 파일로 각 유저별롤 세팅가능한 설정인데 profile 을 설정함으로서 여러개의 credential을 사용할 수 있다.

“`text
[Credentials]
aws_access_key_id = access_key_id1
aws_secret_access_key = secret_access_key1

[profile test]
aws_access_key_id = access_key_id2
aws_secret_access_key = secret_access_key2
“`

아래는 테스트 코드이다. `.boto` 파일에 설정한 내용에 따라 다른 AWS계정의 credential을 사용하게 될 것이다.

“`python
import boto.ec2

# 기본 Credentials 로 되어 있는 설정을 사용
conn = boto.ec2.connect_to_region(‘ap-northeast-1’)
conn.get_all_addresses()

# test 라고 되어 있는 설정을 사용
conn = boto.ec2.connect_to_region(‘ap-northeast-1′, profile_name=’test’)
conn.get_all_addresses()
“`
[/markdown]

파이썬 로깅모듈에 대해서

[markdown]# 파이썬 로깅모듈에 대해서

나는 개발자 경력을 자바개발자로 시작했다.

제일 먼저 배운 메서드는 `main` 메서드이고 그 다음으로 배운건 `System.out.println` 이다.
그러다가 `log4j`라는 고마운 녀석을 알게되어서 별 생각없이 `log4j`만 열심히 쓰다가, 여러 로깅모듈을 하나의 인터페이스로 모아주는 `slf4j`를 살짝 만져보다가 `nodejs`로 전향해서 엄청나게 삽질을 해댄 경험이 있다.

[흑역사 링크](https://github.com/wapj/loggyu)

지금 개발이 메인언어는 `nodejs`이고 프로젝트 빌드 및 배포는 `chef` + `fabric`으로 하고 있고, 서브 스크립트 언어로 `python`과 `shell`을 사용하고 있다. 그중에 스케줄러로 돌아가는 파이썬 스크립트를 만들게 되었는데, 이 녀석이 돌다가 에러가 났을 때 `print` 메서드 만으로는 이게 실행이 됐는지 죽었는지 확인할 길이 없었다. 그래서 파이썬 로깅모듈을 찾아봤다.

![python-logging1.png](http://gyus.me/wp-content/uploads/2014/09/python-logging1-1-1.png)

어라?! 제일 위에 표준 라이브러리가 나온다.

뭔가 내용이 많은데, 표준라이브러리 내용이 읽기 부담 스러운 분은 이 글을 읽으면 조금 도움이 될지도 모르겠다.

일단, 로깅 라이브러리라고 하면 쉽게 말하면 로그를 찍는 기능이 기본이고, 두번째는 여러군데에 찍는 것이고 (예를 들어 아웃풋 스트림에 찍고, 파일로 찍고), 세번째는 이쁘게 찍는것 이다.

그래서 내가 원하는 기능이 있는지 찬찬히 살펴보았다. 필요한 기능은 아래와 같았다.

1. 스트림과 파일에 동시에 로그를 남긴다.
2. 로그를 찍은 시간과 어디에서 로그를 남겼는지 남아야한다.
3. 테스트 환경과 프로덕션 환경에서 남기는 로깅 레벨이 달라야한다.
4. 파일에 남기는 경우, 파일의 크기가 너무 크면 자동으로 하나 더 만들어 주면 좋겠다.
5. 확장이 쉬우면 좋겠다.

일단 결론만 말하면 위에 말한거 전부 다 된다. 기본 모듈이 이정도라니 정말 놀랍다.

차근 차근 한번 알아보자.

### 스트림과 파일에 동시에 로그를 남기기

`print` 메서드로만 로그를 찍어왔다면, 이제 기본 탑재된 `logging` 모듈을 한번 사용해 보자.

“`python
import logging

logging.info(“I told you so”)
logging.warning(“Watch out!”)
“`

위의 코드를 실행하면 아래와 같이 나오는데, 그 이유는 logging의 기본 로그 레벨이 WARNING으로 되어 있기 때문이다.

“`shell
WARNING:root:Watch out!
“`

로그를 전부다 `WARNING`으로 찍을 수는 없으니 살짝만 건드려 보자.

“`python
import logging
logging.basicConfig(level=logging.DEBUG)

logging.debug(“디버깅용 로그~~”)
logging.info(“도움이 되는 정보를 남겨요~”)
logging.warning(“주의해야되는곳!”)
logging.error(“에러!!!”)
logging.critical(“심각한 에러!!”)
“`

그러면 아래와 같이 나올 것이다.

“`shell
DEBUG:root:디버깅용 로그~~
INFO:root:도움이 되는 정보를 남겨요~
WARNING:root:주의해야되는곳!
ERROR:root:에러!!!
CRITICAL:root:심각한 에러!!
“`

로깅 레벨도 지정해 봤으니 파일로도 남겨보자.

“`python
import logging
logging.basicConfig(filename=’./test.log’,level=logging.DEBUG)

logging.info(“=========================================”)
logging.info(“파일에다가 남겨봐요~”)
logging.info(“=========================================”)
logging.debug(“디버깅용 로그~~”)
logging.info(“도움이 되는 정보를 남겨요~”)
logging.warning(“주의해야되는곳!”)
logging.error(“에러!!!”)
logging.critical(“심각한 에러!!”)
“`

위의 코드를 실행하면 실행한 폴더에 test.log라는 파일이 생기고 그 파일을 열어보면 아래와 같이 로그가 파일로 남는다. `여러번 실행하면 파일을 덮어 쓰는 것이 아니라, 기존의 로그에 이어서 붙이기를 하게된다.`

“`shell
INFO:root:=========================================
INFO:root:파일에다가 남겨봐요~
INFO:root:=========================================
DEBUG:root:디버깅용 로그~~
INFO:root:도움이 되는 정보를 남겨요~
WARNING:root:주의해야되는곳!
ERROR:root:에러!!!
“`

그럼 이제 원래 하고 싶었던 걸 해보자.
아웃풋 스트림에도 찍어봤고, 파일로도 남겨 봤는데 둘다 동시에 남길려면 어떻게 해야되지? 라는 질문이 생기는데, 둘다 로그를 남길려면 `logging.getLogger(“로거이름”)` 이라는 메서드로 얻을 수 있는 logger라는 녀석을 사용해야한다.

logger를 써서 여러군데로 로그를 남기는 것에 대해 간단하게 단계를 설명하면 아래와 같다.

1. 로거 인스턴스를 만든다.
2. 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
3. 1번에서 만든 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
4. 로거 인스턴스로 로그를 찍는다.

말로 설명해 봤으니 코드를 보자.

“`python
import logging
import logging.handlers

# 1. 로거 인스턴스를 만든다
logger = logging.getLogger(‘mylogger’)

# 2. 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
fileHandler = logging.FileHandler(‘./myLoggerTest.log’)
streamHandler = logging.StreamHandler()

# 3. 1번에서 만든 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)

# 4. 로거 인스턴스로 로그를 찍는다.
logger.setLevel(logging.DEBUG)
logger.debug(“===========================”)
logger.info(“TEST START”)
logger.warning(“스트림으로 로그가 남아요~”)
logger.error(“파일로도 남으니 안심이죠~!”)
logger.critical(“치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!”)
logger.debug(“===========================”)
logger.info(“TEST END!”)
“`

위의 코드를 실행시켜 보면 콘솔과 파일에 각각 아래와 같은 로그가 남는다.

“`shell
===========================
TEST START
스트림으로 로그가 남아요~
파일로도 남으니 안심이죠~!
치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!
===========================
TEST END!
“`

### 로그를 찍은 시간과 어느 파일의 어느 라인에 심어 놓은 로그인지 남기기

로그의 포매팅에 관한 이야기 인데, 이 부분도 파이썬을 개발하는 분들이 이미 표준 logging모듈에 심어두셨다. 위에서는 핸들러를 알아봤다면 이번에 알아볼 녀석은 포매터라는 녀석이다. 내가 알고 싶은건 날짜와 시간, 파일명, 로그레벨, 메세지 이정도가 되겠다. 코드를 만드는 단계를 설명안 해도 될정도로 엄청 간단하므로 그냥 코드로 바로 알아보도록하자.

“`python
import logging
import logging.handlers

# 로거 인스턴스를 만든다
logger = logging.getLogger(‘mylogger’)

# 포매터를 만든다
fomatter = logging.Formatter(‘[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s’)

# 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
fileHandler = logging.FileHandler(‘./myLoggerTest.log’)
streamHandler = logging.StreamHandler()

# 각 핸들러에 포매터를 지정한다.
fileHandler.setFormatter(fomatter)
streamHandler.setFormatter(fomatter)

# 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)

# 로거 인스턴스로 로그를 찍는다.
logger.setLevel(logging.DEBUG)
logger.debug(“===========================”)
logger.info(“TEST START”)
logger.warning(“스트림으로 로그가 남아요~”)
logger.error(“파일로도 남으니 안심이죠~!”)
logger.critical(“치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!”)
logger.debug(“===========================”)
logger.info(“TEST END!”)
“`

위의 코드를 실행하면 아래와 같이 나온다.

“`shell
[DEBUG|loggingFormatter.py:24] 2014-09-02 20:39:46,630 > ===========================
[INFO|loggingFormatter.py:25] 2014-09-02 20:39:46,630 > TEST START
[WARNING|loggingFormatter.py:26] 2014-09-02 20:39:46,630 > 스트림으로 로그가 남아요~
[ERROR|loggingFormatter.py:27] 2014-09-02 20:39:46,630 > 파일로도 남으니 안심이죠~!
[CRITICAL|loggingFormatter.py:28] 2014-09-02 20:39:46,631 > 치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!
[DEBUG|loggingFormatter.py:29] 2014-09-02 20:39:46,631 > ===========================
[INFO|loggingFormatter.py:30] 2014-09-02 20:39:46,631 > TEST END!
“`

이처럼 포매터의 값만 이리저리 바꿔주면, 내가 원하는 대로 로그를 남길 수 있다!
포매터에 들어가는 변수의 문자열은 아래 링크에서 확인하길 바란다.

[logrecord-attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes)

다음으로 가보자.

### 테스트 환경과 프로덕션 환경에서 로그 레벨을 다르게 하고 싶을경우

나는 테스트환경과 프로덕션 환경을 구분하기 위해서 처음에 서버를 세팅할 때 환경변수를 심어놓는다. nodejs모듈에서 사용되는(expressjs) NODE_ENV라는 환경변수명이 있는데 이 값을 미리 테스트 서버와 알파서버, 프로덕션 서버에 각각 다른 값으로 설정을 해둔다. 여기서는 로컬 개발 머신과 테스트 서버만 있다고 가정하고 예제를 만들어봤다.

“`python
import os
import logging
import logging.handlers

# 로거 인스턴스를 만든다
logger = logging.getLogger(‘mylogger’)

# 포매터를 만든다
fomatter = logging.Formatter(‘[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s’)

# 환경변수를 읽어서 로깅 레벨과 로그를 남길 파일의 경로를 변수에 저장한다
if (os.environ[‘NODE_ENV’] == ‘local’):
loggerLevel = logging.DEBUG
filename = ‘/tmp/test.log’
elif(os.environ[‘NODE_ENV’] == ‘test’):
loggerLevel = logging.DEBUG
filename = ‘/home/www/log/testServer.log’
else:
loggerLevel = logging.INFO
filename = ‘/home/www/log/server.log’

# 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
fileHandler = logging.FileHandler(filename)
streamHandler = logging.StreamHandler()

# 각 핸들러에 포매터를 지정한다.
fileHandler.setFormatter(fomatter)
streamHandler.setFormatter(fomatter)

# 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)

# 로거 인스턴스로 로그를 찍는다.
logger.setLevel(loggerLevel)
logger.debug(“===========================”)
logger.info(“TEST START”)
logger.warning(“파일 명과 로깅 레벨을 각각 환경마다 다르게 남도록 했어요.”)
logger.debug(“디버그 로그는 테스트 환경과 로컬 피씨에서남 남는 답니다.”)
logger.critical(“치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!”)
logger.debug(“===========================”)
logger.info(“TEST END!”)
“`

환경변수 `NODE_ENV`의 값에 따라 로그가 남는 파일의 경로와 로깅 레벨이 달라졌습니다~

### 파일로 로그를 남기는 경우 파일이 너무 커지면 자동으로 새로운 파일을 만들어 줬으면…

보통 이런거는 `shell` 스크립트를 스케줄러로 돌려서 자동으로 돌리거나 하는데, 파이썬에는 `RotatingFileHandler`라는 놈이 이미 만들어져 있다. 그냥 가져다 쓰면 된다. 정말 감동적인 모듈인듯!

이전에 만들어둔 파일 핸들러를 `RotatingFileHandler`로 교체해보자.

위에 있는 fileHandler부분만 아래 코드로 교체하면 된다.
“`python
fileMaxByte = 1024 * 1024 * 100 #100MB
fileHandler = logging.handlers.RotatingFileHandler(filename, maxBytes=fileMaxByte, backupCount=10)
“`

maxBytes 파라메터는 한개의 파일의 최대 바이트 수 이고, backupCount는 몇개까지 백업파일을 남길것인지 세팅하는 파라메터이다.
위의 세팅 대로라면 100MB 짜리 파일을 10개까지 남기겠다. 라는 의미가 된다. 이제 로그 파일의 용량이 엄청나게 커져서 서버에 용량이 부족할까 걱정하지 않아도 된다~ 야호~~!

눈치가 빠른 사람이라면 `logging.handlers` 아래에 다른 핸들러들도 많겠구나~ 라는 생각이들것이다.

[logging.handlers](https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers) 링크를 타고 가보면 많은 핸들러들을 볼 수가 있다.

어지간한 기능은 다 넣어본것 같은데 기존에 없는 기능을 추가할려면 어떻게 하지?!

### 확장이 쉬우면 좋겠다!

에러가 났을 때 mongodb에 그 정보를 저장했으면 좋겠다! 어떻게 하지?

일단 쉬운 방법은 나보다 똑똑한 사람이 만들어 놓은 것을 쓰면 된다. 요즘 세상이 참 좋은 세상이라 구글로 찾으면 내가 생각한건 다있다. ㅎㅎ 근데 가끔 이렇게 찾아도 내 마음에 쏙~ 안들 수도 있다. 그럴 때는 한번 만들어보는 것도 힘들긴 하지만, 도움이 될 때가 많다.

그런 의미에서 다른분들도 이미 뜬 삽이겠지만, 나도 한삽을 더 해보려고 한다. 진짜 기본기능만 되는걸 하나 만들어보자.

참고로, mongodb 모듈로 pymongo가 설치되어 있어야 한다. `pip3 install pymongo`로 간단히 설치가능하다.
mongodb도 물론 설치가 되어있어야한다. 해당 내용은 이글과는 크게 관계없으므로 생략하겠다.

핸들러를 만드는 순서는 아래와 같다.

1. mongodb에 로그를 저장할 수 있도록 handler를 만든다.
2. handler는 logging.Handler를 상속하고 emit 메서드를 구현하면된다.

간단히 만들어본 소스는 아래와 같다.
“`python
import logging
from pymongo.connection import Connection
from bson import InvalidDocument

class MongoHandler(logging.Handler):

def __init__(self, db=’mongolog’, collection=’log’, host=’localhost’, port=None, level=logging.NOTSET):
logging.Handler.__init__(self, level)
self.collection = Connection(host, port)[db][collection]

def emit(self, record):
data = record.__dict__.copy()

try:
self.collection.save(data)
except InvalidDocument as e:
logging.error(“Unable save log to mongodb: %s”, e.message)

if __name__ == ‘__main__’:
MongoHandler(‘mongolog’, ‘test’)
“`

테스트용 소스도 만들어보자. 간단히 핸들러를 추가하고 로그를 찍어본다.

“`python
import logging
from mongoLogger import MongoHandler

if __name__ == ‘__main__’:
logger = logging.getLogger(‘mongoTest’)
logger.setLevel(logging.WARNING)
logger.addHandler(MongoHandler(‘mongolog’, ‘log’))

logger.debug(“test debug”)
logger.info(“test info”)
logger.warning(“test warning”)
logger.error(“test error”)
logger.critical(“test critical”)
“`

실행 후 mongodb에 들어가서 확인을 해보면 아래와 같이 WARNING이상의 로그가 저장되어 있다.

“`shell
> db.log.find().pretty();
{
“_id” : ObjectId(“5405c2cc1626051dcf238cfa”),
“stack_info” : null,
“exc_text” : null,
“exc_info” : null,
“processName” : “MainProcess”,
“lineno” : 11,
“msecs” : 891.3910388946533,
“relativeCreated” : 50.26507377624512,
“process” : 7631,
“name” : “mongoTest”,
“pathname” : “mongoTest.py”,
“created” : 1409663692.891391,
“filename” : “mongoTest.py”,
“funcName” : ““,
“threadName” : “MainThread”,
“msg” : “test warning”,
“args” : [ ],
“module” : “mongoTest”,
“levelno” : 30,
“thread” : NumberLong(“140735296762640”),
“levelname” : “WARNING”
}
{
“_id” : ObjectId(“5405c2cc1626051dcf238cfb”),
“stack_info” : null,
“exc_text” : null,
“exc_info” : null,
“processName” : “MainProcess”,
“lineno” : 12,
“msecs” : 891.618013381958,
“relativeCreated” : 50.492048263549805,
“process” : 7631,
“name” : “mongoTest”,
“pathname” : “mongoTest.py”,
“created” : 1409663692.891618,
“filename” : “mongoTest.py”,
“funcName” : ““,
“threadName” : “MainThread”,
“msg” : “test error”,
“args” : [ ],
“module” : “mongoTest”,
“levelno” : 40,
“thread” : NumberLong(“140735296762640”),
“levelname” : “ERROR”
}
{
“_id” : ObjectId(“5405c2cc1626051dcf238cfc”),
“stack_info” : null,
“exc_text” : null,
“exc_info” : null,
“processName” : “MainProcess”,
“lineno” : 13,
“msecs” : 891.7689323425293,
“relativeCreated” : 50.642967224121094,
“process” : 7631,
“name” : “mongoTest”,
“pathname” : “mongoTest.py”,
“created” : 1409663692.891769,
“filename” : “mongoTest.py”,
“funcName” : ““,
“threadName” : “MainThread”,
“msg” : “test critical”,
“args” : [ ],
“module” : “mongoTest”,
“levelno” : 50,
“thread” : NumberLong(“140735296762640”),
“levelname” : “CRITICAL”
}
“`

### 결론

파이썬에서는 로그를 남기기 위해서 뭘쓸까 고민할 필요가 전혀 없다. 표준 라이브러리가 워낙에 잘되어 있고, 확장 또한 쉽기 때문에 별다른 고민없이 `logging` 모듈만 잘 공부하면 된다. 나도 필요해서 찾아보고 공부해본 것이지만, 위에서 소개한 것 이외에도 많은 기능들을 가지고 있으므로 아마 거의 대부분의 경우에는 표준 logging모듈로도 충분할 것으로 생각된다.

관심이 있는 사람은 [logging-cookbook](https://docs.python.org/3/howto/logging-cookbook.html#logging-cookbook) 페이지를 참고하도록 하자.

[/markdown]

Django tutorial1

[markdown]
#django 튜토리얼1

###쟝고의 기능들
– ORMapper
– Automatic admin interface
– Elegant URL design
– Template system
– cache system
– Internationalization

###쟝고 설치됐는지 확인하기

`python -c “import django; print(django.get_version())”`

###project 만들기

`django-admin.py startproject mysite`

위에꺼 실행하면 아래와 같은 디렉토리 & 파일이 생성됨

“`shell
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
“`

– manage.py : 커맨드라인 유틸리티. 자세한 것은 다음링크에서 확인가능 [django-admin.py and manage.py](https://docs.djangoproject.com/en/1.6/ref/django-admin/)
– mysite 디렉토리 안의 mysite : 디렉토리가 실제 만들게될 프로젝트의 디렉토리
– mysite/settings.py : 쟝고프로젝트 설정파일. 자세한 것은 다음 링크에서 [Django settings](https://docs.djangoproject.com/en/1.6/topics/settings/)
– mysite/urls.py : 쟝고프로젝트의 URL정의 파일. 자세한 것은 다음 링크에서 확인 [URL dispatcher](https://docs.djangoproject.com/en/1.6/topics/http/urls/)
– mysite/wsgi.py : WSGI과 호환되는 웹서버의 시작점. 자세한 것은 다음 링크로 [How to deploy with WSGI](https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/)

##### 서버기동
`python manage.py runserver`

##### 데이터베이스 세팅
디폴트는 sqlite3이고, 다른 데이터베이스를 사용하려면 mysite/settings.py의 `DATABASES` 부분을 수정해주어야한다. (나는 mongodb쓸껀데…) 데이터베이스 세팅에 관한 내용은 다음 링크에 있음 [setting-DATABASE-ENGINE](https://docs.djangoproject.com/en/1.6/ref/settings/#std:setting-DATABASE-ENGINE)

##### 타임존 설정(TIME_ZONE)
나는 대한민국 사람이니 mysite/settings.py 의 TIME_ZONE을 ‘Asia/Seoul’로 변경함. 기본은 ‘America/Chicago’
언어는 귀찮아서 냅둠.

##### 설치된 앱들

settings.py에 보면 INSTALLED_APPS라는 파라메터가 있는데, 현재의 쟝고 인스턴스에서 활성화 된 쟝고 어플리케이션의 이름들을 적어놓은 곳이다. APP은 여러 프로젝트에서 사용가능하고 패키징하고 배포가 가능해서 다른 프로젝트에서도 사용될 수 있다.

디폴트로 포함되어 있는 앱들

– django.contrib.admin – 관리자페이지
– django.contrib.auth – 인증시스템
– django.contrib.contenttypes – 컨텐트타입 프레임웤
– django.contrib.sessions – 세션 프레임웤
– django.contrib.messages – 메세징 프레임웤
– django.contrib.staticfiles – 정적파일을 위한 프레임웤

##### syncdb
`$ python manage.py syncdb`

syncdb 커맨드는 INSTALLED_APPS의 설정을 보고 필요한 데이터베이스 테이블을 생성한다.
처음 실행할때 admin유저를 만들 수 있다.

##### app 만들기
`python manage.py startapp polls`

mysite의 탑레벨 모듈로 polls모듈을 만들자.

디렉토리는 아래와 같이 생겼다.

“`shell
polls/
__init__.py
admin.py
models.py
tests.py
views.py
“`

##### 모델만들기

polls/models.py 를 아래와 같이 고친다.

“`python
from django.db import models

class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField(‘date published’)

class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
“`

하나의 클래스는 테이블과 매핑되고 하나의 변수는 하나의 필드와 매핑이 된다. 그리고 Choice클래스에 외래키를 지정했는데 django는 관계형디비의 모든 관계(다대다 일대다 일대일)를 지원한다.

##### 모델 활성화하기

mysite/settings.py의 파일을 다시 열어서 아래와 같이 수정한다.

“`python
INSTALLED_APPS = (
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘polls’,
)
“`

이제 쟝고가 polls앱이 포함된 것을 알게됐다. 다음으로 아래의 명령을 실행해보자.

`$ python manage.py sql polls`

그러면 아래와 같은 SQL문이 콘솔에 찍힌다.

“`sql
BEGIN;
CREATE TABLE “polls_poll” (
“id” integer NOT NULL PRIMARY KEY,
“question” varchar(200) NOT NULL,
“pub_data” datetime NOT NULL
)
;
CREATE TABLE “polls_choice” (
“id” integer NOT NULL PRIMARY KEY,
“poll_id” integer NOT NULL REFERENCES “polls_poll” (“id”),
“choice_text” varchar(200) NOT NULL,
“votes” integer NOT NULL
)
;
“`

– 콘솔에 찍힌 SQL은 우리가 지정한 혹은 기본인 sqlite3 데이터 베이스에 사용되는 SQL문임을 알 수 있다.
– 테이블명은 자동적으로 app(polls)과 소문자 모델클래스명을 결합하여 만들어진다.
– 프라이머리키(IDs)는 자동으로 추가된다. (오버라이딩 가능)
– 관례로, 쟝고는 ‘_id’를 외래키명에 붙인다.
– 외래키 관계는 REFFERNCE문으로 정확히 만들어졌다.
– `sql`명령어는 실제로 SQL을 실행하지는 않고 단순히 출력만 해준다.

관심있으면 아래 명령어도 테스트해보길

– python manage.py validate – 모델이 유효한지 체크해줌
– python manage.py sqlcustom polls – 테이블 변경이나 제약수정을 위한 명령어를 출력
– python manage.py sqlclear polls – 테이블 삭제를 위한 쿼리를 출력
– python manage.py sqlindexes polls – 인덱스 생성문을 출력
– python manage.py sqlall polls – 해당앱에 사용된 모든 쿼리를 합쳐서 출력

`syncdb`로 모델의 테이블을 생성하자! (syncdb는 create만 해주고 alter는 해주지 않는다. 만약에 컬럼명이 변경된 경우 수동으로 고쳐야함)

### API로 놀기

`$ python manage.py shell` managy.py가 DJANGO_SETTINGS_MODULE의 환경을 세팅해줌

“`python
>>> from polls.models import Poll, Choice
>>> Poll.objects.all()
[]

>>> from django.utils import timezone
>>> p = Poll(question=”What’s new?”, pub_date=timezone.now())
>>> p.save()
>>> p.id
1

>>> p.question
“What’s new?”
>>> p.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=)

>>> p.question = “What’s up?”
>>> p.save()

>>> Poll.objects.all()
[]
“`

마지막에 Poll object라고 나온거는 전혀 도움이 안되니 도움이 되는 문자열로 만들어보자.

polls/models.py 에 다음과 같은 메서드를 추가해주면 됨

“`python
from django.db import models

class Poll(models.Model):
# …
def __unicode__(self): # Python 3: def __str__(self):
return self.question

class Choice(models.Model):
# …
def __unicode__(self): # Python 3: def __str__(self):
return self.choice_text
“`

메서드 하나를 또 추가해보자

“`python
import datetime
from django.utils import timezone
# …
class Poll(models.Model):
# …
def was_published_recently(self):
return self.pub_date >= timezone.now() – datetime.timedelta(days=1)
“`

`python manage.py shell`을 다시실행해서 놀아보자

“`python
>>> from polls.models import Poll, Choice

# Make sure our __unicode__() addition worked.
>>> Poll.objects.all()
[]

>>> Poll.objects.filter(id=1)
[]
>>> Poll.objects.filter(question__startswith=’What’)
[]

# Get the poll that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Poll.objects.get(pub_date__year=current_year)

# Request an ID that doesn’t exist, this will raise an exception.
>>> Poll.objects.get(id=2)
Traceback (most recent call last):

DoesNotExist: Poll matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Poll.objects.get(id=1).
>>> Poll.objects.get(pk=1)

>>> p = Poll.objects.get(pk=1)
>>> p.was_published_recently()
True

>>> p = Poll.objects.get(pk=1)

>>> p.choice_set.all()
[]

>>> p.choice_set.create(choice_text=’Not much’, votes=0)

>>> p.choice_set.create(choice_text=’The sky’, votes=0)

>>> c = p.choice_set.create(choice_text=’Just hacking again’, votes=0)

>>> c.poll

>>> p.choice_set.all()
[, , ]
>>> p.choice_set.count()
3

>>> Choice.objects.filter(poll__pub_date__year=current_year)
[, , ]

>>> c = p.choice_set.filter(choice_text__startswith=’Just hacking’)
>>> c.delete()
“`

여기까지 했으면 인제 파트2로 넘어가도 됨. (튜토리얼은 6개까지 있음)
[/markdown]

3장 얼랭 진짜시작하기

[markdown]
# 3장 진짜 시작하기

[여기](http://learnyousomeerlang.com/starting-out-for-real) 를 요약 정리한 글임을 알려드립니다.

> 얼랭은 상대적으로 작고 심플한 언어이다.(C가 C++보다는 심플한것 처럼)
> 몇가지 기본적인 데이터 타입을 가지고 있음. 이번장에서는 그것들 대부분을 커버해보겠음.
> 앞으로 당신이 작성하게 될 모든 얼랭 프로그램에 사용될 내용을 다루고 있으니 꼭! 읽기를 강추함.

### 숫자

– 얼랭 쉘에서는, 표현식은 공백(빈칸, 개행등)이 뒤따르면 종료되어져야 함. 그렇지 않으면 실행이 안될것임.
– 표현식을 콤마로 구분가능. 그러나 마지막 것의 결과만 보여질것임.

> 얼랭 쉘을 열어서($ erl) 아래의 것들을 쳐보자.

“`
1> 2 + 15.
17
2> 49 * 100.
4900
3> 1892 – 1472.
420
4> 5 / 2.
2.5
5> 5 div 2.
2
6> 5 rem 2.
1
“`

– 얼랭은 integer와 float를 알아서 잘 처리해준다.
– 나누기를 하고 싶을 때 `div`를 나머지를 구하고 싶을 때 `rem`을 사용하시요.
– 수학 명령들은 평범한 우선순위 규칙을 따름(신경안써도 됨)

“`
7> (50 * 100) – 4999.
1
8> -(50 * 100 – 4999).
-1
9> -50 * (100 – 4999).
244950
“`

– 10진수 이외의 진법을 사용하고 싶으면 `진수#값` 이렇게 쓰면 됨
– 기존의 2,8, 16진수 뿐아니라 15진수 36진수(숫자 + 알파벳26자)까지 사용가능

“`
10> 2#101010.
42
11> 8#0677.
447
12> 16#AE.
174
“`

### 불변변수 (Invariable Variables)

– 얼랭으로 산수만 하고 있을 수 없으니 변수를 배워보세.
– 함수형 언어에서는 변수는 변하는 수이어서는 안됨.
– 변수의 기본적인 행위는 아래의 7개의 표현식을 보시길.
– 변수는 항상 `대문자`로 시작하는 것을 잊지말자

“`
1> One.
* 1: variable ‘One’ is unbound
2> One = 1.
1
3> Un = Uno = One = 1.
1
4> Two = One + One.
2
5> Two = 2.
2
6> Two = Two + 1.
** exception error: no match of right hand side value 3
7> two = 2.
** exception error: no match of right hand side value 2
“`
– 위에 6번을 보면 알겠지만, 변수에는 값을 딱 한번만 할당할 수 있음
– 재할당시에 값이 같으면 암말 안하지만, 값이 다른 경우 얼랭 컴파일러가 불평하는 걸 볼 수 있음.
– 좀 더 얘기하면 `=` 연산자는 값을 할당하는 역활 뿐아니라 값이 같은경우 그 값을 리턴하고 다르면 불평을 하게 하는 기능을 가지고 있음.

“`
8> 47 = 45 + 2.
47
9> 47 = 45 + 3.
** exception error: no match of right hand side value 48
“`

– `=` 연산자는 왼쪽에 있는 변수가 값이 할당되지 않은 경우는 오른쪽의 값을 왼쪽의 변수에 할당함. 왼쪽과 오른쪽의 비교 결과는 success가 되서 그 값을 메모리에 저장함.

– `=`연산자가 하는 이런 행동은 `패턴매칭`이라고 하는 것에 기초를 두고 있고, 많은 함수형 프로그래밍 언어에서 가지고 있음. 얼랭은 `=` 요녀석으로 좀더 유연하고 온전한 자신만의 방법을 제공중.
– 변수는 대문자로 시작해야 하지만, `_` 로 시작해도 됨. 그냥 `_` 한글자만 써도 오케이

“`
10> _ = 14+3.
17
11> _.
* 1: variable ‘_’ is unbound
“`

> 얼랭 쉘에서 변수에 잘못된 값을 설정한 경우 `f(변수명)`을 사용하면 변수 초기화 가능. 근데 이함수는 테스트 환경 그것도 쉘에서만 사용가능하고 실환경에서는 사용불가임.

### 아톰(Atoms)

– 변수가 대문자로 시작하면 안되는 단 한가지 이유는 아톰 때문이다.
– 아톰은 리터럴이고 이름과 값을 갖는다.
– 당신이 보는 대로 얻는다. 더이상은 기대하지 말라.
– `cat`이라는 아톰이 의미하는 것은 “cat” 이다.
– 가지고 놀 수 없고, 바꿀 수 없고, 부술 수 없다.
– 소문자로 시작하는 것은 그냥 아톰이다. 더 이상은 없다.
– 작은 따옴표 `’` 로 감싸지는 아톰은 소문자로 시작하지 않아도 된다.
– 아톰이 주로 사용되기 좋은 곳은 데이터와 타입을 짝을 지어야 되는 경우인데, 눈색깔을 표현하기위 해 `BLUE ->1, BROWN ->2, GREEN -> 3, OTHER -> 4` 이런식으로 했었다면, 그냥 아톰으로 `blue`, `brown`, `green`, `other` 단순히 이렇게 하면된다. 만약에 값과 이름이 연결된 것을 정말로 사용하고 싶은 경우는 4장의 `모듈`에서 찾을 수 있다.

“`
1> atom.
atom
2> atoms_rule.
atoms_rule
3> atoms_rule@erlang.
atoms_rule@erlang
4> ‘Atoms can be cheated!’.
‘Atoms can be cheated!’
5> atom = ‘atom’.
atom
“`

– 아톰은 변하지 않는 값을 전달하는 데에는 정말로 좋은 방법이지만, 너무 많은 아톰을 사용하는 것은 문제가 생긴다. “atom table” 이라는 것에서 아톰을 메모리에 올려두고 사용하게 되는데, 아톰 테이블은 GC의 대상이 아니다.
– 그러므로 아톰은 절대로 동적으로 만들어 내지 말라.
– 아래의 아톰들은 예약어로 이미 사용되고 있음.

“`
after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor
“`

### 불린 대수 & 비교 연산자들

“`
1> true and false.
false
2> false or true.
true
3> true xor false.
true
4> not false.
true
5> not (true and true).
false
“`

> `and` 와 `or`은 언제나 연산자 양쪽의 것을 실행한다. 필요한 경우에만 오른쪽의 조건을 판단하고 싶은경우 `andalso`와 `orelse`라는 단축 연산자가 있다.

– 동등과 비동등을 테스트 하는것도 겁나 쉽다. 그런데 다른 언어에서 보던 연산자와는 약간 다르다.

“`
6> 5 =:= 5.
true
7> 1 =:= 0.
false
8> 1 =/= 0.
true
9> 5 =:= 5.0.
false
10> 5 == 5.0.
true
11> 5 /= 5.0.
false
“`

– `==` 대신 `=:=`를 사용
– `!=` 대신 `=/=` 를 사용
– 얼랭은 산술계산에서는 부동소수와 정수를 신경써주지 않는데, 비교를 하면 다르다고 나옴. 이런 경우 사용하는 연산자가 `==`랑 `/=`임
– 비교 연산자는 다른 언어와 동일
– `<` 작다, `>` 크다, `>=` 크거나 같다, `=<` 작거나 같다 - 보통 작거나 같다는 `<=` 이렇게 쓰는데...이건 신택스 에러 발생 `5 + llama`를 실행하면 우째 되나? ``` 12> 5 + llama.
** exception error: bad argument in an arithmetic expression
in operator +/2
called as 5 + llama
“`

– 얼랭은 기본타입을 이상하게 사용하는 것을 안좋아함.

“`
13> 5 =:= true.
false
14> 0 == false.
false
15> 1 < false. true ``` - 얼랭은 다른 타입끼리 비교할 수 있음. - 다른 언어에서는 14는 true이고 15는 false여야 하는데. 얼랭에서는 아니다. 잘 기억해두길! > 각 엘레먼트의 올바른 비교 순서는 다음과 같다.
> `number < atom < reference < fun < port < pid < tuple < list < bit string` > 얼랭을 만든 사람들중 한사람인 조 암스트롱에게 물어보니: “실제적으로 순서는 안중요해요. 그렇지만 전체적인 순서가 잘 정의되어 있다는 건 중요하죠.” 라고 했단다.

### 튜플

– 튜플은 데이터를 조직하는 방법이다.
– 얼랭에서 튜플은 요렇게 적는다. `{Element1, Element2, …, ElementN}`
– 좌표에 대한 튜플을 만든다면 아래와 같이 할 수 있다.

“`
1> X = 10, Y = 4.
4
2> Point = {X,Y}.
{10,4}
“`

– 만약에 Point에서 X값만 필요한 경우는 어떻게 하면되나? => 얼랭에서 값을 변수에 할당할 때 값은 값을 할당을 하는 거면 여러번 할당해도 에러가 안났다는 것을 이용하면 된다. 하기 전에 `f()`로 `X, Y, Point`의 값을 초기화 해줘야함.

“`
3> Point = {4,5}.
{4,5}
4> {X,Y} = Point.
{4,5}
5> X.
4
6> {X,_} = Point.
{4,5}
“`

– 6번 표현식은 익명 변수 `_`를 사용했다. `_`는 항상 할당되어 있지 않다고 여겨지고, 패턴매칭에서 와일드 카드로 사용된다. 튜플의 패턴매칭에서는 좌우의 인자의 숫자가 같아야 한다.

– 튜플은 하나의 값만 다룰 때에도 유용하다.
– 섭씨와 화씨에 대한 값을 가지고 있는 온도를 표현하는 튜플을 만들려면?
“`
9> Temperature = 23.213.
23.213
10> PreciseTemperature = {celsius, 23.213}.
{celsius,23.213}
11> {kelvin, T} = PreciseTemperature.
** exception error: no match of right hand side value {celsius,23.213}
“`

– 11번에서 에러가 났지만 의도한것과 정확히 일치한다. 패턴매칭이 먹혀들었다.
– `=` 연산자로 `{kelvin, T}` 와 `{celsius,23.213}`를 비교했다. 심지어 T는 값이 할당이 되지도 않았다.
– 얼랭은 `celsius`와 `kelvin` 아톰이 다르다고 보았고 에러를 발생시켜서 코드의 실행을 정지 시켰다.
– 하나의 아톰에 하나의 값이 있는 튜플을 `tagged tuple`이라고 부른다.
– 튜플내의 원소는 어떤 타입이어도 상관없다. 심지어 다른 튜플이 들어가도 된다.

“`
12> {point, {X,Y}}.
{point,{4,5}}
“`

### 리스트!

– 의심할 여지 없이 얼랭에서 가장 많이 쓰이는 데이터 타입임.
– 아무거나 담을 수 있다. (숫자, 아톰, 튜플, 다른 리스트)
– `[Element1, Element2, …, ElementN]` 요렇게 사용

“`
1> [1, 2, 3, {numbers,[4,5,6]}, 5.34, atom].
[1,2,3,{numbers,[4,5,6]},5.34,atom]
2> [97, 98, 99].
“abc”
“`

– 2번의 결과는 사람들이 얼랭에서 가장 놀라는 부분임. 왜냐면 아래를 보시라.

“`
3> [97,98,99,4,5,6].
[97,98,99,4,5,6]
4> [233].
“é”
“`

– 얼랭은 리스트안의 숫자중에 글자를 나타내지 않는 숫자가 최소 하나이상 있어야 숫자를 출력한다.
– 얼랭에는 진짜 스트링은 없다!
– 리스트를 두개를 합치려면 `++` 연산자를 사용하고 제거 하려면 `–`연산자를 사용하라

“`
5> [1,2,3] ++ [4,5].
[1,2,3,4,5]
6> [1,2,3,4,5] — [1,2,3].
[4,5]
7> [2,4,2] — [2,4].
[2]
8> [2,4,2] — [2,4,2].
[]
“`

– `++`, `–`는 연산 순서가 오른쪽 우선이다. 오른쪽에서 왼쪽으로 계산된다.

“`
9> [1,2,3] — [1,2] — [3].
[3]
10> [1,2,3] — [1,2] — [2].
[2,3]
“`

– 리스트의 첫번째 엘리먼트는 Head라고 하고, 남은 것들은 Tail이라고 한다. 내장 함수(Built In Functions:BIF)로 그것들을 가져와 보자.

“`
11> hd([1,2,3,4]).
1
12> tl([1,2,3,4]).
[2,3,4]
“`

> 내장함수(BIF)들은 퍼포먼스 향상을 위해 C같이 빠른 언어로 구현되어 있다.

– Head를 더하거나 접근 하는 것은 빠르고, 효과적이다.
– head와 tail을 분리하는 멋진 패턴 매칭이 있는데 바로 이것이다. `[Head|Tail]` 여기 새로운 head를 리스트에 추가하는 예제가 있다.

“`
13> List = [2,3,4].
[2,3,4]
14> NewList = [1|List].
[1,2,3,4]
“`
아래는 리스트로 부터 Head와 Tail을 가르는 예제
“`
15> [Head|Tail] = NewList.
[1,2,3,4]
16> Head.
1
17> Tail.
[2,3,4]
18> [NewHead|NewTail] = Tail.
[2,3,4]
19> NewHead.
2
“`

– `|`는 cons 연산자(컨스트럭터)라고도 한다. 실제로 어떤한 리스트도 cons와 값만 있으면 만들 수 있다.

“`
20> [1 | []].
[1]
21> [2 | [1 | []]].
[2,1]
22> [3 | [2 | [1 | []] ] ].
[3,2,1]
“`

위의 예제는 어떤 리스트도 다음의 공식으로 만들 수 있다는 것을 말해준다.
`[Term1| [Term2 | [… | [TermN]]]]…`
– 리스트는 Head와 그것을 뒤따르는 Tail을 재귀적으로 정의한 것으로 여겨질 수 있다.
– 리스트는 지렁이랑 비슷한다. 머리를 자르면 지렁이가 두마리가 되는 이치이다.

![지렁이](http://learnyousomeerlang.com/static/img/worm.png)
– 얼랭의 리스트는 때때로 헷갈릴 수 있는데, 이 개념에 익숙해지려면 다음 예제를 다 읽어보라.(힌트 : 모두 같은 리스트이다.)

“`
[a, b, c, d]
[a, b, c, d | []]
[a, b | [c, d]]
[a, b | [c | [d]]]
[a | [b | [c | [d]]]]
[a | [b | [c | [d | [] ]]]]
“`

– 위의 것이 이해가 되었다면 리스트 컴프리헨션을 다룰 준비가 됐다는 것이다.

> `[1|2]` 이런 방법으로 리스트를 표현 하는 것을 ‘부적합한 리스트’라고 한다. 부적합한 리스트는 패턴매칭은 성공하지만, 얼랭의 표준 함수에서 사용될 때 실패하는데(length()같은거), 얼랭의 함수는 적합한 리스트를 받게 되어 있기 때문이다. 적합한 리스트는 가장 마지막 셀에 빈 리스트를 포함한다. `[2]`이렇게 선언하면 해당 리스트는 자동으로 적합한 리스트로 변환된다. `[1|[2]]`도 잘 동작한다! 부적합한 리스트는 문법적으로는 유효하지만, 아주 많은 제약이 있다.

### 리스트 컴프리헨션 (리스트 조건제시법, 리스트 내포)

– 리스트 컴프리헨션은 리스트를 만들거나 수정하는 방법들이다.
– 리스트를 다루는 다른 방법 보다 프로그램을 짧고 이해하기 쉽게 해주는 방법이다.
– 처음에는 이해하기가 힘들긴한데, 노력을 할 만한 가치가 있다.
– 개념을 집합에서 가져왔다.

다음과 같은 집합 표기가 있다고 하자.
![](http://learnyousomeerlang.com/static/img/set-comprehension.png) x는 실수이고, 자기자신과 자기자신을 제곱한 값이 같은 집합을 의미하는데, 그 결과과 되는 집합은 `{0,1}`이다. 다른 집합 표기의 예제를 보자. 축약해서 `{x: x > 0}` 이라고 쓰겠다. 우리가 원하는 것은 x > 0인 값이다.

– 리스트 컴프리헨션은 다른 집합으로 부터 집합을 만들어낸다. `{2n : n in L}`이라는 집합이 있고, L이라는 리스트가 `{1,2,3,4}`일 때 이것의 얼랭에서의 구현은 아래와 같다.

“`
1> [2*N || N <- [1,2,3,4]]. [2,4,6,8] ``` 수학식과 얼랭의 표기법을 비교해보면 크게 많이 다르지 않다. 중괄호({})는 대괄호([])가 되고 콜론(:)은 두개의 파이프(||)가 되고 'in'이라는 글자는 화살표(<-)가 된다. 단순히 기호만 바꾸는 것으로 같은 로직을 유지할 수 있다. 위의 예에서 `N`은 [1,2,3,4]각 각의 값에 순차적으로 패턴매칭된다. 화살표(<-)는 `=` 연산자와 동일하게 작동하지만, 예외가 발생한 경우 예외를 무시한다. 리스트 컴프리헨션에 불린 값들을 이용해서 제약들을 추가 가능하다. 1부터 10까지의 숫자에서 짝수만 가져오고 싶다면 아래와 같이 할 수 있다. ``` 2> [X || X <- [1,2,3,4,5,6,7,8,9,10], X rem 2 =:= 0]. [2,4,6,8,10] ``` `x rem 2 =:= 0`는 숫자가 짝수인지 체크한다. 좀 더 실용적인 예제로 식당의 메뉴중에 $3와 $10 사이의 메뉴에는 7%의 세금이 붙는 경우를 살펴보자. ``` 3> RestaurantMenu = [{steak, 5.99}, {beer, 3.99}, {poutine, 3.50}, {kitten, 20.99}, {water, 0.00}].
[{steak,5.99},
{beer,3.99},
{poutine,3.5},
{kitten,20.99},
{water,0.0}]
4> [{Item, Price*1.07} || {Item, Price} <- RestaurantMenu, Price >= 3, Price =< 10]. [{steak,6.409300000000001},{beer,4.2693},{poutine,3.745}] ``` 위의 예제로 숫자가 딱떨어지게는 안나왔지만, 얼랭에서의 리스트 컴프리핸션을 다음과 같은 방식으로 사용하면 될 것이라는 감은 올 것이다. `NewList = [Expression || Pattern <- List, Condition1, Condition2, ..., ConditionN]` `Pattern <- List`는 제너레이터 표현이라고 부른다. ``` 5> [X+Y || X <- [1,2], Y <- [2,3]]. [3,4,4,5] ``` - 위의 예제는 `1+2`, `1+3`, `2+2`, `2+3` 이렇게 실행된다. - 리스트 컴프리헨션의 레시피를 좀 더 일반화 할 수 있다. `NewList = [Expression || GeneratorExp1, GeneratorExp2, ..., GeneratorExpN, Condition1, Condition2, ... ConditionM]` - 제너레이터 표현은 패턴매칭과 연결되어서 필터처럼 동작한다는 것을 주의하시라. ``` 6> Weather = [{toronto, rain}, {montreal, storms}, {london, fog},
6> {paris, sun}, {boston, fog}, {vancouver, snow}].
[{toronto,rain},
{montreal,storms},
{london,fog},
{paris,sun},
{boston,fog},
{vancouver,snow}]
7> FoggyPlaces = [X || {X, fog} <- Weather]. [london,boston] ``` - 리스트 'Weather'의 엘리먼트와 {X, fog}가 매칭되지 않아도 그냥 무시한다. `=`이었으면 에러가 발생했을 것이다. ### 비트 문법 - 대부분의 언어에는 숫자, 아톰, 튜플, 리스트, 구조체 등등을 다루는 것을 지원한다. - 그리고 바이너리 데이터를 다루는 것도 대부분 지원한다. - 얼랭은 패턴매칭에서 한단계 더 나아가서 바이너리 데이터를 다루는 것에 대한 추상화를 지원한다. - 비트를 다루는 것은 유니크한 문법을 가지고 있고 처음 보기엔 이상하지만, 비트와 바이트가 일반적으로 어떻게 동작하는지 안다면 이해가 될 것이다. **어렵다면 남은 부분들은 뛰어 넘어도 된다.** 비트 문법은 <<와 >>로 바이너리 데이터를 감싼후, 콤마로 구분된 읽을 수 있는 세그먼트 별로 나눈다. 하나의 세그먼트는 바이너리의 비트의 시퀀스이다.(꼭 바이트로 구분되어야 되는 건 아니지만, 그게 디폴트이다.) 오렌지의 색을은 포토샵이나 이런데서 #RRGGBB형식으로 나타내면 #F09A29 이런식으로 표현 가능하다. 이걸 얼렝으로 표현하면 다음과 같다.

“`
1> Color = 16#F09A29.
15768105
2> Pixel = <>.
<<240,154,41>>
“`

위의 예제는 기본적으로 “바이너리 값 #F09A29을 24비트 공간에 놔주시요.(Red 8비트, Green 8비트, Blue 8비트)”라는 뜻이다. 값은 파일로 쓰는 것도 가능! 파일은 바이너리 파일로 저장되므로 텍스트에디터에서는 이상한 글자로 나오지만, 얼랭에서 다시 읽으면 제대로 <<240,154,41>>이라고 나옴

더 흥미로운 것은 바이너리를 패턴매치로 풀어낼 수 있는(to unpack) 기능이 있다는 것이다.

“`
3> Pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
4> <> = Pixels.
** exception error: no match of right hand side value <<213,45,132,64,76,32,76, 0,0,234,32,15>>
5> <> = Pixels.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
“`

위의 예제의 3번 명령에서 우리는 4개의 RGB색의 픽셀을 바이너리로 지정했다.
4번 명령에서는 바이너리에서 4개의 데이터로 풀어낼려고 했으나 에러가 발생! 왜냐하면 실제로 12개의 값이 있는데 4개로 받아서 그렇다. 그래서 5번 명령에서 각각의 값이 24비트데이터인것을 얼랭에게 알려줬는데 그것이 바로`Var:24`의 뜻이다. 우리는 하나의 픽셀을 다시 각각의 색으로 풀어낼 수 있다.

“`
6> <> = <>.
<<213,45,132>>
7> R.
213
“`

첫번째 말고 다른것도 다 필요한 경우는? 얼랭은 이런 경우를 위해 더 많은 문법설탕과 패턴매칭을 할 수 있다.

“`
8> <> = Pixels.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
9> R.
213
“`

멋지지 않은가? 얼랭은 한가지 이상의 방법으로 바이너리 세그먼트를 접근한다.
아래의 것은 모두 유효하다.

“`
Value
Value:Size
Value/TypeSpecifierList
Value:Size/TypeSpecifierList
“`

> __Type__
> 가능한 값들 : `integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32`
> 이것은 바이너리 데이터로 사용되는 것을 종류를 말한다. ‘bytes’는 단순히 ‘binary’를 줄여서 부르는 것임을 주의하라. 마찬가지로 ‘bits’는 ‘bitstring’의 줄임말이다. 타입이 없는 경우는 얼랭은 ‘integer’ 타입으로 가정한다.

> __Signedness__
> 가능한 값들 : `signed | unsigned`
> 타입이 integer인 경우에 매칭을 위해 사용되는 것이다. 디폴트는 ‘unsigned’

> __Endianness__
> 가능한 값들 : `big | little | native`
> Endianness는 integer, utf16, utf32, float를 위한 것이다. 시스템이 어떻게 바이너리 데이터를 잃는가와 관계가 있다. 예를 들어 BMP이미지 헤더의 포맷은 4바이트의 인티져로 되어 있다. 파일이 72바이트 인경우 little-endian시스템은 이것을 `<<72,0,0,0>>`으로 나타낸다. 그리고 big-endian은 <<0,0,0,72>>로 나타낸다. 하나는 ’72’로 읽히고 다른 하나는 ‘1207959552’로 읽어진다. 그러므로 올바른 endianness를 잘 체크해야한다. ‘native’옵션은 런타입에 little-endian인지 big-endian인지 선택한다. 디폴트는 ‘big’

> __Unit__
> unit:Integer로 쓰여짐
> 이것은 비트들에서 각 세그먼트의 사이즈를 말한다. 허용범위가 1..256까지인 쎗이고 인티저는 디폴트 1로, float와 bit string은 8비트 바이너리가 디폴트이다. utf8, utf16, uft32는 unit이 필요없다. unit 사이즈는 byte-alignment의 보장에 사용되곤 한다.

TypeSpecifierList는 내장되어 있고 ‘-‘로 구분되는 어트리뷰트이다.

“`
10> <> = <<-44>>.
<<"Ô">>
11> X1.
212
12> <> = <<-44>>.
<<"Ô">>
13> X2.
-44
14> <> = <<-44>>.
<<"Ô">>
15> X2.
-44
16> <> = <<72>>.
<<"H">>
17> N.
72
18> <> = <<72>>.
<<"H">>
19> <> = <<72,0,0,0>>.
<<72,0,0,0>>
20> Y.
72
“`

바이너리 데이터를 읽는 여러가지 방법이 있다는 것을 이제 알았을 것이다. 좀 헷갈리지만, 대부분의 언어에서 제공하는 다른 툴들에 비해서는 아직 많이 심플하다.

표준 바이너리 명령어들(시프트연산, ‘and’, ‘or’, ‘xor’, ‘not’)또한 얼랭에 포함 되어 있다. `bsl`(Bits Shift Left), `bsr`(Bit Shift Right), `band`, `bor`, `bxor`, `bnot`

“`
2#00100 = 2#00010 bsl 1.
2#00001 = 2#00010 bsr 1.
2#10101 = 2#10001 bor 2#00101.
“`

이러한 문법과 일반적인 비트구문을 사용한다면, 바이너리 데이터의 파싱과 패턴매칭은 누워서 떡먹기이다. 어떤 사람은 TCP세그먼트를 다음과 같은 코드로 파싱 하기도 했다.

“`
<> = SomeBinary.
“`

같은 로직을 비디오 인코딩, 이미지, 다른 프로토콜의 적용등등에 사용할 수 있다.

> __사이다를 너무 많이 마시지는 마세요__
얼랭은 C나 C++에 비해서 느리다. 만약에 당신이 참을성이 부족한 사람이라면 얼랭으로 비디오 컨버팅이나 이미지 변환 작업을 하는 것은 좋지 않은 아이디어이다. 그럼에도 불구하고 바이너리 문법은 엄청나게 재미있다. 얼랭은 단지 무거운 수 덩어리들을 다루기에는 좋지 않을 뿐이다.

> 그렇지만 위와 같은 수치처리가 필요하지 않다면 얼랭은 여전히 이벤트 반응성 또는 메세지 전달 (극단적으로 가벼운 아톰도 도움을 준다.) 어플리케이션을 위해서는 어마무시하게 빠르다. 이벤트를 백만분의 일초단위로 처리할 수 있기때문에 소프트한 리얼타임 어플리케이션에 적합하다.

바이너리 표기는 다른 면도 가지고 있는데, 비트문자열이다. 비트문자열은 리스트로 표현하는 문자열과 같고, 언어에 강하게 연결되어 있지만, 비트문자열은 메모리 공간을 좀더 효율적으로 사용할 수 있게해준다. 왜냐하면 리스트로 된 문자열은 링크드리스트로 되어 있지만, 비트 문자열은 C 배열과 비슷하다. 비트 스트링의 문법은 다음과 같다.`<<"this is a bit string!">>` 리스트로 된 문자열에 비해서 메모리 절약 및 패턴 매칭을 통한 조작도 간단하기 때문에 결과적으로 바이너리 스트링을 사용한다.

> `주의` 아톰이 가볍다고 스트링 대신 사용해서는 안된다. 문자열은 쪼갠다든지, 정규식을 활용한든지 해서 여러가지로 다뤄질 수 있지만, 아톰은 그렇게 될 수 없다.

### 바이너리 컴프리헨션

리스트 컴프리헨션이 리스트를 위한 것이듯, 바이너리 컴프리헨션은 비트 문법을 좀 더 짧고 간결하게 하기 위한 것이다. 바이너리 컴프리헨션은 상대적으로 얼랭에서 신기능에 속한다. 그래서 R13B버젼 이전버젼은 모듈을 불러와야한다. 그 이후는 표준으로 포함되어 있다.

“`
1> [ X || <> <= <<1,2,3,4,5>>, X rem 2 == 0].
[2,4]
“`

리스트 컴프리헨션과 다른 점은 `<-`이 `<=`로 바뀌었다는 점과 리스트([])대신 바이너리 (<<>>)를 사용했다는 것 뿐이다. 위에서 RGB값을 각각의 픽셀로 나타내는 것을 연습했었는데, 구조가 커질 수록 유지보수가 힘들어진다. 이럴경우 바이너리 컴프리헨션을 사용하면 좀 더 깔끔해진다.

“`
2> Pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
3> RGB = [ {R,G,B} || <> <= Pixels ]. [{213,45,132},{64,76,32},{76,0,0},{234,32,15}] ``` 온전한 바이너리 컴프리헨션은 기본적으로 바이너리 데이터를 인티저를 가지고 있는 튜플로 변환시킨다. 또다른 바이너리 컴프리헨션은 정확히 반대의 기능을 한다 ``` 4> << <> || {R,G,B} <- RGB >>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
“`

결과적으로 생성되는 바이너리는 제너레이터가 바이너리를 리턴할 경우 정확히 사이즈를 명시하지 않으면 안되니 주의하도록 하자.

“`
5> << <> || Bin <- [<<3,7,5,4,7>>] >>.
** exception error: bad argument
6> << <> || Bin <- [<<3,7,5,4,7>>] >>.
<<3,7,5,4,7>>
“`

위와같이 정해진 사이즈를 주고 바이너리 컴프리헨션을 사용할 수도 있다.

> 이글을 쓰는 지금은 바이너리 컴프리헨션은 아직 잘사용되지 않고 문서도 매우 빈약하다. 그러므로 기본적인 동작을 이해하는 것에 더 의미를 두자. 비트 문법에 대해 더 알려면 [얼랭의 스펙](http://user.it.uu.se/~pergu/papers/erlang05.pdf)을 읽어보는 것도 좋다.
[/markdown]