이니셜라이저 – Swift

Swift의 클래스와 구조체, enum 객체들은 사용하기 전에 반드시 초기화되어야 한다. 그러면 초기화(initialization)이란 무엇인가? 객체의 생성 자체를 초기화과정에 포함시키는 관점과 그렇지 않은 관점이 있지만, 여기서는 “객체를 만들어서 사용가능한 상태로 준비하는 일”이라고 보자. let foo = Foo() 와 같이 특정한 타입의 인스턴스를 생성하는 구문을 실행했을 때 저 아래(?)에서 벌어지는 과정은 다음과 같다.

이니셜라이저 – Swift 더보기

(연재) SQLite3 강좌 – 테이블에서 조회하기 2

지난 글에서 SQLite3에서 SELECT 명령의 사용방법에 대해서 살펴보았는데, FROM을 통해서 단일 테이블 혹은 단일 테이블 내의 범위를 부분적으로 얻어내는 서브 쿼리를 통해서 보다 정교한 범위의 데이터를 얻고, 또 WHERE절을 사용해서 결과를 필터링 하는 방법에 대해서도 살펴보았다. 그외에 GROUP BY나 그외 aggregation 연산을 통한 쿼리 방법에 대해서는 자세하게 다루지 않았는데, 그 전에 JOIN에 대해서 간단하게 짚고 넘어가고자 한다.

JOIN 이란 무엇인가

JOIN은 간단히 정의하자면 “두 개 이상의 그리드를 연결해서 하나의 그리드처럼 만드는” 것이다. 테이블 하나를 하나의 그리드라고 보면 두 개의 테이블을 옆으로 이어붙이는 것이라 이해하면 된다. 그런데 무턱대고 두 개의 그리드를 이어붙일 수는 없다. (심지어 두 그리드는 행의 개수가 다를 수도 있다) 논리적으로 두 개의 그리드를 옆으로 이어붙이기 위해서는 하나의 ‘접합점’이 필요하다. 이 접합점은 바로 두 테이블에서 같은 값들이 들어가는 공통된 칼럼이다.

예를 들어 발매된 음반의 정보를 담고 있는 albums라는 테이블과 수록곡이 등재된 tracks라는 테이블이 있다고 가정하자. tracks 테이블에는 각각의 트랙이 수록된 앨범 정보를 나타내는 album_id 라는 칼럼이 있고, 이 칼럼은 albums 테이블의 id 칼럼을 참조하는 외래 키(foreign key)로 설정되어 있다고 하자. (꼭 외래키로 지정되어 있을 필요는 없다. 그저 공통된 값이 있으면 된다.) 그렇다면 tracks 테이블의 album_id 칼럼과 albums 테이블의 id 칼럼을 접합점으로 해서 두 개의 테이블을 나란히 연결할 수 있다. 이렇게 연결된 테이블을 사용하면 특정한 노래에 대해서 노래가 수록된 앨범의 제목이나 앨범의 발매 년도 등, 앨범 관련한 정보를 함께 참조할 수 있게 된다.

SQLite3는 세 가지 조인 방식을 지원하고 있는데, INNER JOIN, LEFT JOIN, CROSS JOIN 이 그것이다. 보다 큰 규모의 다른 DBMS에서는 RIGHT JOIN이나 OUTER JOIN, FULL OUTER JOIN 등의 옵션도 지원하는 경우가 있다. 여기서는 SQLite3의 JOIN 방식에 대해서 알아보자.

INNER JOIN

INNER JOIN은 가장 흔하게 쓰이는 JOIN 방식으로 두 개의 테이블이 공통된 칼럼을 가지고 있고, 해당 칼럼의 값이 같은 레코드끼리 연결한다. 이 때 두 그리드가 연결되는 방식은 교집합과 비슷하다. 만약 앨범 정보가 없는 트랙이 있거나, 혹은 앨범 정보는 있지만, 해당 앨범의 수록곡 정보가 없는 경우가 있다면 이들 한쪽만 정보가 있는 칼럼은 INNER JOIN 에서는 제외된다.

JOIN의 일반적인 문법은 다음과 같다.

SELECT {결과칼럼...} FROM {Table1} [as alias1]
{INNER|LEFT|CROSS} JOIN {Table2} [as alias2]
ON {연결조건}  / USING (일치칼럼)
...
  • FROM 절에 쓰이는 테이블 명이 왼쪽에 오는 그리드가 된다. as 를 통해서 별칭을 붙여줄 수 있다.
  • JOIN의 타입과 오른쪽에 올 그리드를 지정한다. 역시 테이블 명 뒤에는 as를 통해서 별칭을 붙여줄 수 있다.
  • 두 테이블이 접합에 사용하는 칼럼 외에도 같은 이름의 칼럼을 가지고 있다면 테이블명.칼럼명 이나 별칭.칼럼명의 형태로 지정할 수 있다.
  • 통상 특정 칼럼의 값이 같아야 하니 ON table1.colA = table2.colB 와 같은 식으로 조건을 정의한다.
  • 만약 두 테이블의 공통 칼럼이 이름이 같고, 그 칼럼의 값이 같은 행끼리 연결한다면 USING (col) 로 축약해서 쓸 수 있다.

JOIN으로 연결한 결과는 하나의 그리드로 보게되며, 따라서 SELECT 문의 WHERE, ORDER BY LIMIT, ㅌ을 그대로 사용할 수 있다. 예를 들어 각 노래의 제목과 그 노래가 수록된 앨범명을 보고 싶다면 다음과 같이 쿼리를 작성할 수 있다.

아래 쿼리는 http://www.sqlitetutorial.net/tryit/ 에서 테스트 해 볼 수 있다.

SELECT name, title FROM tracks 
-- name은 tracks에서 곡 제목을, 
-- title은 albums에서 앨범 제목을 나타내는 칼럼이다.
INNER JOIN albums ON tracks.almubid = albums.albumid;

각 앨범의 수록곡 개수를 세고 싶다면 albumid 별로 그룹핑해서 각 트랙의 개수를 세면 된다.

-- 앨범별 수록곡 개수
SELECT title, count(name) FROM tracks
INNER JOIN albums USING (albumid)
GROUP BY albumid
ORDER BY title;

JOIN은 그리드를 왼쪽에서 오른쪽으로 붙여나간다고 하였다. 따라서 2개, 3개, 4개의 테이블을 연이어 조인할 수 있다.

-- 각 트랙과 수록된 앨범, 아티스트 명을 출력한다.
SELECT tracks.name, title, artists.name
FROM tracks
INNER JOIN albums USING (albumid)
INNER JOIN artists USING (artistid)
WHERE trackid < 200; -- 결과가 너무 많아져서 붙임

LEFT JOIN

INNER JOIN에서는 접합 칼럼을 기준으로 조건에 맞는 행이 없는 경우는 모두 탈락시킨다고 하였다. LEFT JOIN은 말 그대로 접합부의 왼쪽 그리드를 기준으로 삼는 JOIN 방식이다. 따라서 INNER JOIN과는 달리 일치하는 값이 없는 경우라도 접합점의 왼쪽에 있는 테이블의 행은 알 수 없는 값을 모두 NULL로 채워서 결과를 만든다. 따라서 기준이 되는 테이블에서는 누락되는 행 없이 결과를 조회할 수 있다.

음반 관련 데이터베이스에는 artists라는 테이블이 있다. 여기에는 앨범을 발매한 가수 혹은 작곡가로 참여한 사람의 고유 식별자와 이름이 기재된다. 이 때 전업 작곡가의 경우에는 artists 테이블에는 이름이 기록되지만, 발매한 앨범이 없기 때문에 INNER JOIN으로 albums 테이블과 조인하면 이들의 이름이 포함되지 않는다. 사람의 이름을 기준으로 앨범 발매 여부와 상관없이 앨범 타이틀과 아티스트명을 조회하려면 다음과 같이 한다.

-- 아티스트와 그 아티스트가 발매한 앨범
SELECT name, title FROM artists
LEFT JOIN albums ON artists.artistid = albums.artistid;

두 테이블 A와 B 가 있을 때, INNER JOIN의 경우 A를 기준으로 B를 접합하는 경우와 B를 기준으로 A를 접합하는 경우의 쿼리는 다르지만 그 결과는 동일하다. 어차피 접합점이 되는 칼럼을 기준으로 결과가 생성되기 때문이다. 따라서 INNER JOIN의 경우 양쪽 테이블 위치에 상관없이 결과 자체는 대칭이라 할 수 있다. 하지만 LEFT JOIN은 왼쪽에 위치하는 테이블을 기준으로 행이 정의되기 때문에 위 쿼리에서 albums를 FROM에 쓴다면 결과가 달라질 것이다.

SELECT count(*) FROM albums
LEFT JOIN artists USING (artistid);  -- 347

SELECT count(*) FROM artists
LEFT JOIN albums USING (artistid); -- 418

참고로 3개 이상의 테이블을 연결하는 것은 앞에서부터 연결된 결과 그리드에 뒤의 테이블을 누적해서 더하는 개념이며 개별 테이블이 더해지는 개념이 아니다. 다음 쿼리는 아티스트와 그 발매 앨범, 앨범별 수록곡의 수를 쿼리하는데, 두 개의 LEFT JOIN이 들어간다. artists – albums – tracks가 연결되는데, albums와 tracks는 albums.albumid = tracks.albumid 로 연결되는데,  (A + B ) + (A + C) 의 개념이 아니라 (A + B) + C 와 같은 식으로 테이블이 합쳐진다고 볼 수 있다.

-- 아티스트의 각 앨범과 앨범 수록곡의 수
SELECT a.name, title, count(b.trackid)
FROM artists as a
LEFT JOIN albums USING (artistid)
LEFT JOIN tracks USING (albumid)
GROUP BY b.albumid;

SELF JOIN

특정한 조건에 해당하는 행을 찾기 위해서 가끔 어떤 테이블과 그 테이블 자신을 JOIN 하는 경우가 있다. 이것을 셀프 조인이라고 하는데, 실제로 많이 쓰이는 케이스이다. 예를 들어 employees라는 테이블을 생각해보자. 여기에는 각 직원의 이름과 성, 그리고 이 직원의 직속상사(보고를 받는 사람)가 누구인지에 대한 정보를 가지고 있다고 하자. 이 때 각 직원의 이름과 그 직속상사의 이름을 짝지은 결과를 조회하기 위한 쿼리를 보자. 참고로 두 개의 문자열을 연결하기 위해서는 || 연산자를 사용할 수 있다.

-- 각 직원의 풀네임과 그 직속 상관의 풀네임
SELECT a.firstname || ' ' || a.lastname AS fullname,
       b.firstname || ' ' || b.lastname AS directReportsTo
FROM employees a
INNER JOIN employees b ON a.reportsto = b.employeeid
;

JOIN에서는 그저 두 개의 그리드가 횡방향으로 연결되는 것이기 때문에 셀프 조인은 특별한 것이 아니다. 다만 두 개의 테이블 이름이 똑같기 때문에 혼동을 피하기 위해서 두 개의 그리드 모두에 각각의 별칭을 붙여서 사용한다. 별칭의 경우 as 를 써도 되고 쓰지 않아도 된다. (MySQL등 JOIN시 테이블 별칭을 쓸 때 as을 쓰지 않도록 강제하는 경우도 있으니 참고한다.)

참고로 위에서는 INNER JOIN을 썼는데, LEFT JOIN을 쓰면 어떻게 될까? reportsTo 칼럼이 NULL인 레코드가 검색될 수 있다. 그렇다면 이 사람은 누구에게도 보고하지 않아도되는, CEO가 될 것이다.

CROSS JOIN

CROSS JOIN은 극히 특별한 케이스에서 좀 유용할 수 있다. (사실 왜 특별하게 CROSS JOIN 이라고 이름이 붙었는지는 알 수 없는데…) 두 테이블 A, B에 대해서 JOIN이 수행될 때, ON 이나 USING 으로 한정하는 절이 없으면, 실질적으로 발생하는 데이터는 두 테이블의 데카르트 곱이다. 테이블 A가 m 개 행을 가지고 있고, 테이블 B가 n개 행을 가지고 있다면, 두 테이블의 데카트르 곱은 A의 1행이 B의 1 … n 의 모든 행에 대응되는 결과를 만들고, 다시 A의 2행이 B의 모든 행에 대응되고… 이런식으로 m * n 개의 행을 가진 모든 조합의 경우를 만들어 낼 수 있다. CROSS JOIN은 그 특성상 매우 큰 그리드를 생성하므로 다시 한 번 말하지만, 주의해서 사용해야 한다.

굳이 CROSS JOIN으로 명시하지 않더라도 INNER JOIN, LEFT JOIN을 접합 조건 없이 사용하거나, 단순히 FROM 에서 두 개의 테이블을 컴마로 나열하기만 해도 같은 효과를 얻을 수 있다.

이상으로 SQLite에서 여러 테이블을 조인하는 방법에 대해서 살펴보았다. 다시 한 번 정리하자면 JOIN은 두 개의 그리드를 옆으로 이어붙이는 용도로 사용하며, 두 개 이상의 그리드에 대해서 적용이 가능하다. 그 중에서 INNER JOIN과 LEFT JOIN이 많이 쓰이며, INNER JOIN은 두 그리드의 공통 칼럼 값을 기준으로 결과를 생성하고, LEFT JOIN은 두 그리드 중 왼쪽의 것을 기준으로 결과를 생성한다는 차이가 있다. JOIN을 사용하게 되면 각각의 테이블에 분산되어 있는 데이터를 하나로 묶어서 쿼리할 수 있고, JOIN으로 묶여진 결과는 하나의 큰 테이블처럼 생각할 수 있다.

파이썬은 처음이라 – 값이란 건 처음이라

이번 글에서는 파이썬에서의 값에 대해서 살펴보겠다. 프로그래밍 언어를 배울 때에는 흔히 문법이나 알고리듬, 자료 구조와 같은 부분에 집중하면서 “값”이라는 부분, 즉 데이터 자체에 대해서는 소홀해지는 경향이 있다. 하지만 컴퓨터 프로그램은 본질적으로 데이터를 다루는 논리적인 기계장치이며, 어떤 입력값을 조작/변형처리하여 출력하는 구조를 가진다. 그 과정에서 다루어지는 값에 대한 특성을 이해하는 것은 값외의 다른 모든 것들을 이해하는 것과 같은 비중으로 중요하다.

컴퓨터에서 모든 값은 0과 1로 구성되는 이진수만이 통용된다는 것이 널리 알려져 있다. 그렇다면 어떻게 정수나 실수1, 텍스트와 같은 값들을 다룰 수 있는 것일까. 이러한 궁금증에서부터 시작하여 프로그래밍 언어에서의 값의 종류(타입)와 더불어 파이썬에서 사용되는 원시 값의 종류에 대해서 살펴보도록 하겠다.

값과 타입

컴퓨터는 전기적 신호를 통해 작동하는 기계이며, 전기신호의 ON/OFF를 구분하며, 이 ON/OFF를 조합하여 서로 다른, 즉 구분이 가능한 신호의 조합을 만들 수 있다. 이 때 ON을 1에, OFF를 0에 대응하면 일련의 ON/OFF 신호의 조합은 10110101011… 과 같은 표현으로 해석할 수 있다. 두 개의 숫자로 이루어지는 이 숫자체계를 수학에서는 이진수라고 하며, 약간의 산수를 통해서 이진수는 우리가 흔히 이해하는 10진수로 바꿔서 표시할 수 있다.

0과 1로 만들어지는 이진수는 결국 어떤 수(number)일 뿐인데, 우리가 개념적으로 사용하는 여러 가지 정보는 정확하게는 수 개념에만 한정되지 않는다. 실제로 우리는 수를 표현하기 위해서 숫자체계라는 것을 이용해서 숫자기호라는 문자를 사용한다. 사과에 오렌지를 더할 수 없듯이 “1”이라는 숫자2에 하나라는 의미의 수 1을 더할 수는 없다. 이렇 듯 우리의 관념에서도 어떤 정보는 그 타입이 구분되며, 어떤 타입들은 서로 호환되지만3 어떤 타입들은 서로 호환되지 않을 수도 있다.

정수나 float 값은 실수(Real Number[^3])라는 수 체계에서 볼 때는 같은 값이지만, 컴퓨터에서는 이진수를 사용해서 이를 표현하고 관리하기 위해서 서로 다른 기술적인 체계를 따르게 된다. 따라서 흔히 숫자라고하는 타입은 정수와 실수로 나뉘며, 보통의 프로그래밍 언어에서는 이들을 구분한다. 그외에 프로그래밍 언어에서 사용되는 가장 기초적인 데이터 타입에는 다음과 같은 것들이 있다.

  • 정수 : 0, 1, -1 과 같이 소수점 이하 자리가 없는 수. 수학에서의 정수 개념과 동일하다. 파이썬에서는 int 타입이라고 한다.
  • 실수 : 0.1, 0.5 와 같이 소수점 아래로 숫자가 있는 수. 파이썬에서는 float 이라고 한다.
  • 문자, 문자열 : 숫자 “1”, “a”, “A” 와 같이 하나의 낱자를 문자라 하며, 이러한 문자들이 1개 이상있는 단어/문장와 같은 텍스트를 문자열이라고 한다. 파이썬에서는 str 타입으로 분류한다. 특히 파이썬은 낱자와 문자열 사이에 구분이 없이 모두 str 타입을 적용한다.
  • 불리언값 : 참/거짓을 뜻하는 대수값. 보통 컴퓨터는 0을 거짓, 0이 아닌 것을 참으로 구분한다. 파이썬에는 bool 타입이라 부르며, 이 타입에는 TrueFalse 의 두 멤버만 존재한다. (참고로 각각은 대문자를 써야 한다.)
  • None : 존재하지 않음을 표현하기 위해서 “아무것도 아닌 것”을 나타내는 값이다.

이러한 기본적인 데이터 타입을 조합하여, 여러 개의 값을 하나의 단위로 묶어서 다루는 데이터 타입이 있다. 논리적으로 이들은 데이터 타입인 동시에 데이터의 구조(흔히 말하는 자료 구조)의 한 종류이기도 하다. 보통 다른 데이터들을 원소로하는 집합처럼 생각되는 타입들이다.

  • 리스트 : 순서가 있는 원소들의 집합. 파이썬에서 가장 중요한 데이터 타입 중 하나이다.
  • 튜플 : 순서가 있는 원소들의 묶음. 리스트와 혼동하기 쉬운데 단순히 하나 이상의 값을 묶어서 하나로 취급하는 용도로 사용된다.
  • 사전 : 그룹내의 고유한 이름인 키와 그 키에 대응하는 값으로 이루어지는 키값 쌍(key-value pair)들의 집합이다.
  • 집합/셋(set) : 순서가 없는 고유한 원소들의 집합.

자료 구조에 해당하는 데이터 타입은 매우 중요한데, 이 글에서 한 꼭지로 다루기에는 너무 중요하기 때문에 각각 별도의 글에서 다룰 예정이다.

숫자값들

수를 나타내는 타입으로 정수와 실수가 있다고 했다. 각각은 int 타입과 float 타입이다. 더하기, 빼기등의 산술 연산을 적용하는 값이며 가장 기본적인 수 개념들을 표현한다.

리터럴

어떤 값을 “써서 표현하는 방법”을 리터럴이라고 한다. 수를 표현하는 기본 리터럴은 다음과 같다.

  • 정수의 경우, 그냥 숫자들로 숫자값을 쓰면 된다. 0, 100, 123 과 같은 표현을 쓴다.
  • 실수의 경우, 중간에 소수점이 들어간다. 0.1,  4.2, 3.13123 와 같은 식으로 쓴다.
  • 0. 으로 시작하는 실수값에서는 흔히 앞에 시작하는 0을 뺄 수 있다. .5 는 0.5를 줄여쓴 표현이다.
  • 부호를 나타내는 – , +를 앞에 붙일 수 있다. (-1, +2.3 등)

보통 우리가 숫자를 쓰는 체계는 10진법이다. 파이썬에서는 드물게 다른 진법으로 숫자를 표현하기도 한다.

  • 기본적으로 그냥 숫자만 사용하는 경우, 이는 십진법 값으로 해석된다.
  • 파이썬이 인식할 수 있는 숫자 리터럴 체계에는 10진법외에도 이진법, 8진법, 16진법이 존재한다.
  • 이진법 숫자는 0b로 시작한다. 0b1010110 은 십진법 86을 숫자로 쓴 것이다.
  • 8진법 숫자는 0o로 시작한다. 위의 86은 8진법으로 썼을 때 0b126이다.
  • 16진법숫자는 0x로 시작한다. 16진법은 숫자외에도 a~f 까지의 문자를 포함하며, 이 글자들은 대소문자를 구분하지 않는다. 86은 16진법으로 표현시 0x56이다.

한편 큰 숫자를 다룰 때에는 _를 쓸 수 있다. 우리가 일상생활에서 큰 자리 숫자를 표현할 때 세자리마다 컴마를 찍는 것과 비슷하게 숫자 리터럴 중간에 _ 를 쓰는 것은 무시된다. 백만을 쓸 때 1_000_000 이라고 표기하면 정상적으로 1백만으로 인식되는데 1000000으로 쓰는 것보다 읽기 쉬을 수 있다. 언더스코어는 세자리마다 써야 하는 것은 아니고 원하는 아무자리에나 쓰면 된다.

숫자는 아니지만 참/ 거짓을 의미하는 부울대수값이 있다. 이들은 그 자체가 키워드로 True/False를 사용하여 표현한다.

True, False 가 키워드가 된 것은 파이썬3의 일이다. 파이썬2에서는 상수처럼 쓰이는 그냥 이름이었다. 따라서 True = False 와 같은 괴상한 구문이 아무런 문제없는 파이썬 코드였다.

연산

값을 가지고는 여러 연산을 할 수 있다. 다음은 기본적으로 지원되는 연산이다.

  • 더하기, 빼기, 곱하기, 나누기 :  각각 +, -, *, / 문자를 사용한다. 수식을 표현하는 방식은 계산기를 사용하는 방식과 똑같다.
  • 몫과 나누기 : // , % 를 사용한다.  (7 // 5, 13 % 8)
  • 거듭제곱 : ** 를 사용한다 ( 3 ** 2, 4 ** 3)
  • 비트연산 : & (and), | (or), ^ (xor), ~ (not) 이 있고, 시프트에는 << , >> 를 사용한다.
  • 비교연산 : 동등 및 대소를 비교할 수 있다.  참고로 ‘대소’비교는 ‘전후’비교가 사실은 정확한 표현이다. 비교 연산은 숫자값 뿐만 아니라 문자열에 대해서도 적용할 수 있다.
    • == , != : 같다, 같지 않다.
    • <, <=, >, >= : 작다, 작거나 같다, 크다, 크거나 같다. (좌변 기준으로)
  • 멤버십연산 : 멤버십 연산은 특정한 집합에 어떤 멤버가 속해있는지를 판단하는 것으로 비교연산에 기반을 둔다.
    • is, is not : 값의 크기가 아닌 값 자체의 정체성(identity)이 완전히 동일한지를 검사한다.
    • in, not in : 멤버십 연산. 어떠한 집합 내에 원소가 포함되는지를 검사한다. ('a' in 'apple')
  • 논리연산 : 비교 연산의 결과는 보통 참/거짓이다. 이러한 불리언값은 다음의 연산을 적용받는다. 참고로 불리언외의 타입의 값도 논리연산을 적용받을 수 있다. 논리연산을 위한 평가는 뒤에서 설명하겠다.
    • and :  두 값이 모두 참일 때 참
    • or : 한 값이 참이면 참
    • not : 단항 연산으로 우변의 값을 반전한다.

표현식

어떤 값 혹은 값들과 연산자를 함께 사용해서 수식을 표현한 것을 표현식(expression) 이라고 한다. 표현식을 평가식이라고도 하는데, 그 자체로 평가되어 하나의 결과값으로 축약된다. 따라서 1 + 1 과 같은 수식도 표현식이며, 0 과 같이 값 리터럴로 값을 표현해놓은 것 그 자체도 표현식이 될 수 있다. 이어서 소개할 문자열 역시 그 자체가 값이므로 표현식이며, 문자열과 관련되는 연산자들도 있다. 표현식은 궁극적으로 “평가”되며, 평가된다는 것은 표현식은 결국 하나의 값으로 수렴한다는 의미이다. 표현식은 간단한 수식으로 취급되고 있으나, 언어의 구조의 근간을 이루는 매우 중요한 개념이다.

앞에서 bool 타입이 아닌 숫자나 문자등의 다른 값들이 참/거짓으로 평가될 때 다음과 같이 판단된다.

  • bool : True는 참, False는 거짓이다.
  • int / float : 0이되면 거짓, 그외의 값은 참으로 평가한다.
  • str : 길이가 0인 빈 문자열은 거짓, 그외에는 참으로 평가한다.
  • 리스트, 사전 : 빈 컨테이너는 거짓, 그외에는 참으로 평가한다.
  • 임의의 객체 : None이면 거짓이며, None이 아닌 경우 값으로 평가하여 위의 규칙을 따르게 한다.

문자열

문자열은 글자 혹은 글자가 모여서 만드는 단어나 문장을 말한다. 크게는 이런 단어, 문장이 모여서 여러 줄의 단락이나 글 전체가 하나의 문자열이기도 하다.

리터럴

문자열 리터럴의 방식은 기본적으로 따옴표를 사용하는 것이다. 파이썬에서는 큰 따옴표와 작은 따옴표를 구분하지 않고 모두 문자열 리터럴에 쓴다. 하지만 괄호처럼 양쪽 따옴표가 맞아야 한다. (문자열을 둘러싸는 따옴표와 다른 따옴표는 문자열 내의 일반 글자로 해석된다. )

  • "apple" , 'apple' 은 모두 문자열 리터럴로 apple이라는 단어를 표현한 것이다.
  • 두 개의 문자열 리터럴이 공백이나 줄바꿈으로 분리되어 있는 경우에 이것은 하나의 문자열 리터럴로 해석한다. "apple,"   "banan""apple,banana"라고 쓴 표현과 동일하다.

따옴표를 세 개 연이어 쓰는 방법도 문자열 리터럴의 한 방법이다. 따옴표 세 개를 연이어서 쓰는 경우에는 문자열 내에서 줄바꿈이 그대로 허용된다. 흔히 함수나 모듈의 간단한 문서화 텍스트를 표현할 때 많이 쓰인다.

"""He said "I didn't go to 'SCHOOL' yesterday"."""  
=> He said "I didn't go to 'SCHOOL' yesterday". 를 그대로 표현할 수 있다.

=> 여러 줄에 대한 내용을 쓸 때.
'''HOMEWORK:
1. print "hello, world"
2. print even number between 2 and 12
3. calculate sum of prime numbers up to 100,000
''' 

그외에 이스케이프를 허용하지 않는 raw string 리터럴, 다른 값을 삽입하는 format string 리터럴, 바이트배열을 정의하는 bite string 리터럴등이 있는데, 조금 특수한 케이스이므로 여기서는 다루지 않겠다. (각각 r'...', f'...', b'...'와 같은 식으로 쓴다는 것 정도만 알고 넘어가자.)

연산

문자열도 엄연한 값이며, 가능한 연산이 있다.

  • 문자열 + 문자열 : 두 문자열을 연결할 수 있다.
  • 문자열 in 문자열 : 문자열 내의 특정 글자가 있는지 검사한다. ('a' in 'apple', 'c' not in 'banana')
  • 문자열 * 정수 : 문자열를 정수값만큼 반복하여 문자열을 만든다 ('abc' * 3 --> 'abcabcabc')

문자열에 실수를 곱하거나 문자열에 정수를 더하는 연산은 우리가 그냥 생각해도 어째야 할지를 모르겠고, 실제로도 정의되지 않았다. 이와 같은 연산들은 모두 ValueError 에러를 내게 되니 참고하자.

내삽 (interpolation)

내삽은 문자열에 대한 특별한 연산이다. 예를 들어 "Tom has 3 bananas and 4 apples." 라는 문자열이 있다고 할 때, 이것을 리터럴로 정의하는 것은 그 내용이 소스코드에 고정되는, 이른 바 하드 코딩(hard coding)이다. 문자열은 한 번 생성된 이후로 변경되지 않는 불변의 고정값이므로 Tom이 가지고 있는 사과나 바나나의 개수가 바뀌었을 때, 그 내용을 적절히 변경해 줄 수가 없다. 내삽(interpolation)은 문자열내에 동적으로 변할 수 있는 값을 삽입하여 상황에 따라 다른 문자열을 만드는 방법이다. 문자열 내삽의 기본원리는 문자열 내에 다른 값으로 바뀔 치환자를 준비해두고, 필요한 시점에 치환자를 실제 값의 내용으로 바꿔 문자열을 생성하는 것이다. 내삽의 방법에는 다음의 세 가지 방법이 있다.

  1. 전통적인 포맷 치환자를 사용하는 방법 : 문자열 % (값, ...)의 형식을 이용해서 문자열 내로 변수값을 밀어넣는 방법
  2. 문자열의 .format() 메소드를 사용하는 방법 : 치환자의 구분없이 사용할 수 있으며, 각 값을 포맷팅할 수 있는 장점이 있다.
  3. 문자열 포맷 리터럴을 사용하는 방법 : 포맷 메소드를 사용하지 않고 리터럴만으로 2.의 방법을 사용할 수 있다 (파이썬 3.6 이상)

전통적인 포맷 치환 방법

문자열 내에 어떤 값을 집어넣는것에 대한 필요는 아주 오래전부터 있어왔고, 이러한 치환자의 종류와 형식은 파이썬이 만들어지기 이전부터 일종의 표준으로 정의되어 자리잡고 있었다. 문자열 치환자는 퍼센트 문자 뒤에 포맷형식을 붙여서 치환자를 정의한다. 치환자를 포함하는 문자열과 각 치환자에 해당하는 값의 튜플을 (튜플을 아직 배우지는 못했지만…) % 기호로 연결하여 표현한다.

주요 치환자에는 다음과 같은 종류가 있다.

  • %d : 정수값을 나타낸다. d 앞에는 자리수와 채움문자를 넣을 수 있다. 예를 들어 %04d 라고 쓰면 앞의 0은 채움문자이고 뒤는 포맷의 폭이다. 즉 %04d 는 0으로 시작하는 네자리 정수를 의미한다. 13이라는 값을 포맷팅할 때, %d 에 치환하면 “13”이 되지만, %04d 에 치환되는 경우에는 “0013”으로 치환된다.
  • %f : 실수값을 나타낸다. f 앞에는 .3 과 같이 소수점 몇 째자리까지 표시할 것인지를 결정하는 확장정보를 넣을 수 있다. "%.3f" 라는 템플릿은 1.5를 “1.500”으로 표시해준다.
  •  그 외에 정수값은 숫자 리터럴과 같이 %b, %o, %x를 이용해서 각각 이진법, 8진법, 16진법으로 표시할 수 있다.
  • %s : 문자열을 의미한다.
  • %r : “representation”으로 타입을 구분하지 않는 값의 표현형을 말한다. 표시되고자 하는 값의 타입이 분명하지 않을 때 사용한다. 위에서 소개된 %d, %f, %s 에 대해서 올바르지 않은 타입의 값을 치환하려 하면 TypeError가 발생한다. 이럴 때 사용할 수 있다.

format 메소드를 사용하는 방법

파이썬의 기본 내장함수 중에서도 format() 함수가 있는데, 이 함수도 문자열의 format 메소드와 동일한 동작을 한다. format(문자열, 치환값)` 의 형식으로 사용하며 그외 내용은 이 절에서 설명하는 것과 동일하다.

전통적인 치환자구분이 타입을 가린다는 제약이 있고, 포맷팅의 방법이 제한되어 있다는 부분때문에 최근에 대세를 이루는 방식이다. 이 방식에서는 %d 와 같은 표현 대신에, { } 를 사용한다. { } 자체가 하나의 값을 의미하며, 번호를 부여해서 한 번 받은 값을 여러번 사용할 수 있다. 이 포맷팅 방식을 미니포맷이라고 불리는데 대략 다음과 같은 문법을 가지고 있다.

{ 치환자 }

치환자  ::= [필드이름] [!변환형] [: 포맷정의]
포맷정의 ::= [[채우기]정렬][부호][#][0][폭][그룹옵션][.소수점자리][타입]

뭔가 포맷방식이 엄청난데, 채우기와 폭은 전통적인 포맷 치환자에서도 지원하던 것인데, 왼쪽 정렬이나 중앙정렬도 설정할 수 있다. 숫자값인 경우에는 특별히 d, f, x, b, o 등의 표현타입도 정의해줄 수 있다. 이 부분을 더 깊게 파고들려면 많은 분량이 필요하므로, 앞으로 진도를 나갈 때 예제 등에서 사용하면서 설명하도록 하겠다.

None

객체의 개념에 대해서 아직 설명하기 전이라 None을 명확하게 설명하기는 어렵다. 다만 어떤 값이 없는 상태를 가리킬만한 표현이 마땅히 없기 때문에 “아무것도 없다”는 것으로 약속해놓은 어떤 값을 하나 만들어 놓은 것이다. None 이라고 대문자로 시작하도록 쓰며, 실제 출력해보아도 아무것도 출력되지 않는다. 값이 없지만 False 나 0 과는 다르기 때문에 어떤 값으로 초기화하기 어려운 경우에 쓰기도 한다.

 

정리

이상으로 가장 기본적인 파이썬의 값 타입들에 대해서 살펴보았다. 물론 리스트와 사전, 그리고 튜플과 같은 다른 타입들이 있지만, 분량 관계상 이 글에서 계속 다루는 것 보다는 이쯤에서 한 번 끊고 가는 것이 좋겠다. 계속해서 다음 포스팅으로 이어나가시라….


  1. floating number. 0.5와 같이 소수점 이하 자리를 갖는 수를 말한다. 
  2. 이 글에서는 관례를 따라 숫자값을 표현할 때는 숫자만 쓰고, 문자 자체를 표현할 때는 따옴표를 적용할 것이다. 
  3. 1 + 0.5 를 계산할 수 있듯이 정수와 실수는 몇 가지 연산에서 호환이 가능하다. 이 타입간 호환은 사용하는 프로그래밍 언어에 따라 제약이 있을 수 있다. 

LiveScript 살펴보기 – 03 함수

LS에서 함수는 일반 문법 편에서 잠깐 언급했듯이 화살표를 써서 간단히 정의할 수 있다. 이 함수 표현에서 중요한 점 두 가지는 첫 째 우변은 하나 이상의 표현식이라는 점과 표현식이 순서대로 나열되는 경우 맨 마지막 표현식의 결과가 자동으로 리턴된다는 것이다.

함수

LS는 함수형 프로그래밍 언어의 스타일을 많이 도입했다고 하였다. 비록 LS가 진짜 순수한 함수형 언어는 아니지만, 함수형 언어의 스타일을 도입한다는 것은 LS내의 함수라는 것은 가급적 아래와 같은 특징을 갖도록 디자인되어야 한다는 것이다.

  1. 순수성 : 함수의 결과값이 순수하게 파라미터에만 의존할 것. 따라서 입력된 인자가 같다면 항상 리턴될 출력값도 같음을 보장한다.
  2. 간결성 : 사실 억지로 만든 말이기는 한데, 함수에 대한 연산 (커링, 바인딩, 파이핑, 합성)이 다양하고, 함수 자체가 1급 시민인 점(이는 JS로부터 자연스럽게 물려받는 특징이다.)에 착안하여, 가능한 간결하고 명료한 함수들을 정의하고, 이러한 함수들을 조합하여 필요한 함수를 생성하는 방법을 지향하는 것이다.

이전에 예에서 리스트의 최대값을 찾는 getMax 함수를 잠깐 언급한 바, 있는데 이 함수는 JS로 쓴다면 아래와 같을 것이다.

function getMax(arr) {
  if(arr.length < 1) { return null }
  var x = arr[0];
  var i = 1;
  var result = x;
  while(i < arr.length) {
    if (x < arr[i] ) {
      x = arr[i];
      result = x;
    }
    i += 1;
  }
  return result;
}

그리고 LS에서는 다음과 같이 쓴다.

get-max = (arr) -> arr.reduce (>?)

너무 심하게 부풀려서 비교한다고 생각할 수 있는데, 물론 JS에서도 함수형 스타일로 맵/필터/리듀스를 할 수 있는 API가 Array 타입에 존재하고 있다. 따라서 위 LS 코드를 JS로 쓴다면 (그것은 마치 함수형 스타일의 JS 코드겠지만) 다음과 같이 쓸 수 있다.

var getMax = function(arr){ 
  return arr.reduce(function(x, y){ 
    return x > y ? x : y; 
  }
);

다만, 여기서 말하고자 하는 것은 LS로 쓰면 함수를 엄청 짧은 코드로 쓸 수 있다는 점이 아니라, “두 수 중에서 큰 수를 판단하는 함수”를 리스트의 각 원소에 대해서 순차적으로 적용하면 리스트 내에서 가장 큰 수를 찾을 수 있다”는 간단한 아이디어에 관한 것이다. 두 개의 연산(함수)에 대해 이해하고 그것을 연관지어 원하는 기능을 쉽게 조합할 수 있는 것이 함수형 스타일의 가장 큰 특징이라 할 수 있겠다.

리턴

함수의 본체가 하나의 표현식인 경우에는 LS의 함수 정의 문은 one-liner로 작성된다. 두 개 이상의 표현식이 함수의 본체를 구성하는 경우에는 -> 뒤에서 줄을 바꾸고 들여써서 블럭을 구분하여 표기할 수 있다.

times = (x, y) -> x * y
sum = (arr) ->
  s = 0
  for i in arr
    s += i
  s

마지막 표현식은 자동으로 리턴되므로, 명시적으로 리턴이 없는 함수를 정의하고 싶다면 !-> 기호를 써서 정의한다.

호출

함수 호출 시, ()를 생략한다. 또한 인자들은 callable하지 않다면 콤마를 생략하고 나열할 수 있다. 인자를 받지 않는 함수는 !를 써서 호출됨을 표시한다. 그리고 인자 뒤에 오는 and, or, xor, 한 칸 띈 후의 ., ?. 등은 모두 암묵적으로 호출 구문을 완료한다. 따라서 공백을 기준으로 체이닝 표기를 간단히 할 수 있다.

$ \h1 .find \a .text! #=> h1 a 의 내용
# $('h1').find('a').text()

f!
[1 2 3].reverse!slice 1 #= [2, 1]

익명함수의 경우, 인자가 없는 케이스에는 do를 -> 에 쓰면 이를 호출한다. 간단하게 익명함수를 이용해서 여러 표현식을 묶은 블럭을 만들고 실행하는 방법으로 이해.

do -> 3 + 2 
#=> 5
#(function(){
#  return 3 + 2;
#})();

do를 이름이 있는 함수1와 썼을 때, 그리고 do가 표현식에 쓰인게 아니라면 이름지은 함수는 유지된다. 즉, do를 평가하기 전에 함수를 참조할 수 있고, do 문을 만났을 때 한 번 더 실행하는 것이다.

i = 0
f 9 #f를 한 번 실행. i == 1
i #=> 1
do function f x # 여기서 한 번 더 실행
  ++i
  x
i # => 2

축약된 객체 블럭 문법은 함수 호출 구문에서 사용할 수 없다. 단, 한 줄에 쓰여진 리터럴은 인식된다. 함수의 인자 중 하나로 객체 블럭을 쓰고자 한다면 do를 이용해서 블럭을 명시해야 한다.

func
  a:1
  b:2
## 컴파일 되지 않음

## 대신 이렇게 쓸 수 있다.
func a:1, b:2

## 보통은 do를 사용한다.
func do
  a: 1
  b: 2

이처럼 do는 함수 호출 시에 요긴하게 많이 쓰인다. 특히 개별 인자를 각 라인의 표현식으로 사용하려할 때 유용하다.

함수의 중위표현

파라미터를 2개 받는 함수는 백팃으로 둘러 싸서 중위연산자처럼 쓸 수 있다.

add = (x, y) -> x + y
2 `add` 3 #=> 5

g = (a, b) ->
  add ...     # 함수 내에서 쓰이는 `...`은  인자를 그대로 넘긴다는 의미로 해석할 수 있다. 
# 11

그리고 함수의 본체 내에서 …을 쓰면 암묵적으로 모든 인자의 리스트로 인식한다. 이는 특히 super를 호출할 때 유용하다.

파라미터

파라미터는 표현식을 쓰는 표기로도 확장된다. 즉 객체나 다른 파라미터에 관한식으로 파라미터를 받으면, 해당 값으로 맵핑된다는 이야기이다. 이를 통해서 함수 본체에서 각 파라미터간의 관계를 재설정해야 하는 부담을 줄 일 수 있다. 아래의 예제를 보자.

set-person-params = (
  person
  person.age        # 두 번째 인자는 첫 번째 인자의 .age 키에 배당된다.
  person.height ) -> person

p = set-person-params {}, 21, 180cm
# {age: 21, height: 180}

# 'this' 확장하기
set-text = (@text) -> this
# var setText = function(text){ this.text = text; return this; }

위의 setPersonParams() 함수는 세 개의 인자를 받는데, 그 중 두 번째, 세 번 째 인자는 첫 번째 인자의 프로퍼티로 명시되어 있다. 따라서 함수 호출 시에 해당 값이 주어지면, 이는 첫번째 인자로 넘겨진 객체의 프로퍼티로 자동으로 세팅된다. 따라서 함수 본체 내에서 person.age = age와 같은 처리를 따로 하지 않고 인자 자체를 확장하여 person.age로 쓰는 것으로 대체가능하다.

특히 이 확장은 객체의 메소드나 함수를 작성할 때, 특히 this를 다룰 때 매우 간결하게 쓰일 수 있다.

디폴트 값

파라미터에는 디폴트 값을 미리 지정해줄 수 있으며, (파이썬 스타일), 좀 헷갈릴 수는 있는데, 논리 연산을 수행하여 디폴트값/오버라이드를 적용할 수 있다.

add = (x && 4, y || 3) -> x + y
# x는 무조건 4로 오버라이드되고
# y는 없으면 3이 된다.

또한 객체를 통으로 받아서 특정 키를 분해해 낼 수 있다.

set-coords = ({x, y}) -> "#x, #y"
set-coords y:2, x:3 #=> "3, 2"

# 그리고 그 와중에 다시 디폴트 값을...
set-coords = ({x = 1, y = 3}) -> "#x, #y"

그리고 ...y 등과 같이 일련의 인자들을 하나의 리스트로 취급하는 것도 가능하다.

f = (x, ...ys) -> x + ys.1
f 1 2 3 4 #=> 4

이외에도 캐스팅 연산자를 파라미터에 붙일 수 있는데, 그러면 자동으로 평가된 후 들어간다.

f = (!!x) -> x
f 'truely' # true

g = (+x) -> x
g ' ' # 0

obj = {prop: 1}
h = (^^x) ->
  x.prop = 99
  x
h obj
obj.prop # 1

파라미터의 생략

JS의 함수는 호출 시 파라미터 개수에 크게 구애받지 않는다. 즉 선언된 파라미터보다 부족한 개수의 인자가 넘겨지면, 빈 인자는 undefined를 갖게되고, 인자가 과하게 많이 넘겨지면 함수의 로컬 스코프에 매핑되지 못한 인자는 모두 무시된다.

LS의 함수에 있어서 정의를 파라미터 없이 함수를 만들었다 하더라도 함수 본체에서는 파라미터를 참조하는 것이 가능하다. 사실 이는 JS의 스펙에 정의된 arguments2 객체에 의한 것이다.

단 인자 함수의 파라미터는 it으로 지칭한다. 그외의 파라미터는 모두 &0, &1 과 같은 식으로 번호 순서대로 참조하여 처리할 수 있다.

바운드 함수

바운드 함수는 특정 객체에 소유권이 묶인 함수를 말한다. 보통 함수 내에서 this를 참조하는 것은 해당 함수를 호출한 문맥이 되는데, 바운드 함수는 처음 바운드한 시점의 문맥이 유지된다. 바운드 함수의 자세한 내용에 대해서는 별도로 찾아보도록 하고, 바운드 함수를 작성하는 것은 ~> 를 써서 웨이브진 화살표를 쓴다는 점만 기억하자.

obj = new
  @x = 10
  @normal = -> @x  
  @bound = ~> @x

obj2 = x: 5
obj2.normal = obj.normal
obj2.bound = obj.bound

obj2.normal! # this.x 이고 이 때 this는 obj2 이므로 5
obj2.bound # 10. bound메소드는 obj에 바인딩되어 있으므로, 내부의 this는 obj를 가리킨다.

흔히 바운드 함수는 특정 객체의 메소드를 이벤트 핸들러를 사용하며, 그 내부에서 this를 참조할 때 유용하다.

- 대신 ~를 쓰는 것이 바운드 함수를 의미한다는 것은 매우 일관적으로 적용되며, !~>, ~~>, ~function 등의 표현이 그대로 사용될 수 있다.

커링

커링은 하스켈 커리의 이름을 따서 명명되었는데, 모든 다 인자 함수는 단인자 함수들이 합성된 상태로 볼 수 있다는 것을 말한다. 예를 들어 두 정수를 더하는 add 라는 함수가 있다면 다음과 같이 정의하고 호출할 수 있다.

add = (x, y) -> x + y
add 3 2

이 때, 이 호출식을 (add 3) 2라고 생각하는 것이다. 그러면 add 3은 정수 하나를 받아서 3을 더한 값을 리턴하는 단인자 함수가 된다.

그렇다면 다시, adda 라는 정수를 받아서 “b라는 정수를 받아 여기에 a를 더해서 리턴하는 함수”를 리턴하는 함수라고 생각할 수 있다.

add = (a) ->
  (b) -> a + b

add-one = add 1
add-one 2
# 3

이렇게 커링을 이용하면 쉽게 부분적용된 함수를 만드는 것이 가능해진다. 그리고 LS에서는 자동으로 커리되는 함수를 만들 수 있는 선언법으로 -->를 쓰는 것을 지원한다. 역시 같은 맥락에서 바운드된 커리드 함수는 ~~> 으로 선언할 숭 있다.

add = (a, b) --> a + b
add-one = add 1
add-one 2 #=> 3

접근자 단축

접근자 메소드/함수의 경우에 맵이나 필터 동작에 적용되는 경우 예를 들어 (x) -> x.prop과 같은 식으로 처리하는 것은 (.prop)으로 줄여 쓸 수 있다. 이는 특정 프로퍼티를 호출하는 것을 괄호로 둘러싼 것이기 때문에 메소드 호출 역시 같은 식으로 처리할 수 있다.

map (.length) <[ hello there you ]> #=> [5, 5, 3]
filter (.length < 4), <[ hello there you ]> #=> ['you']

map (.join \|) [[1 2 3], [7 8 9]]
#=> ['1|2|3', '7|8|9']

반대로 (obj.) 이라고 쓰는 것은 (it) -> (obj.it)의 단축 표현이 된다.

obj = one:1, two:2, three:3
map (obj.) <[one, three]> #=> [1, 3]

부분 적용

표현식 내에 언더스코어(_)를 사용하여 해당 표현식을 1개 인자로 받는 함수로 간주하고, 언더 스코어는 해당 인자의 위치를 표시하는 플레이스 홀더로 생각할 수 있다. 이는 커리드 함수에서 적절한 위치에 있지 않은 파라미터를 가변으로 남기고 싶을 때 유용하다.

# 여기서 쓰인 filter는 별도로 정의된 top-level의 함수라 가정한다.
filter = (fn, arr) --> arr fn
# 이 떄, 특정한 리스트에 대해서 고정하고 필터링 함수만 변경하려할 때,
# 다음과 같이 쓰게 되는데
filter-nums = (fn) -> filter fn, [1 to 5] # 
# 인자로 받게되는 fn의 위치를 `_`를 이용해서 표시해주면 된다.
filter-nums = filter _, [1 to 5]


filter-nums (<3) # [1, 2]

이러한 부분적용된 함수는 특히 고차 함수를 파이핑으로 연결할 때 유용하다. 아래 예제는 underscore.js를 이용해서 특정한 리스트를 조작하는 코드이다. “인자로 받은 객체가 다시 인자로 전해질 때”를 상정하기 때문에, _의 사용이 혼동되지 않고 해석될 수 있다.

[1 2 3]
|> _.map _, (* 2)
|> _.reduce _, (+), 0
# => 12

백 콜

콜백으로 주어지는 표현식들은 결국 블럭으로써, 들여쓰기를 적용해야 한다. 백 콜은 이러한 방식을 거꾸로 표현하여 콜백을 들여쓰지 않고 표현할 수 있게 해준다.

map (-> it * 2), [1 to 3] 

# 백 콜로 전환
x <- map _, [1 to 3]
x * 2  # 여기서부터는 _ 에 들어갈 표현을 쓸 수 있다. 

백 콜은 콜백을 들여쓰지 않게 해주기 때문에, 중첩되는 콜백지옥을 깔끔하게 처리해주는 장점을 가지고 있다. 만약 top레벨의 코드 중간에 백 콜을 쓰게 된다면, 이후 끝라인까지의 모든 내용이 콜백 내의 코드로 간주된다. 백 콜의 코드가 중간에서 끝나야 한다면 미리 do를 사용해서 들여쓰기 블록을 시작해주자.

do
  data <-! $.get 'ajaxtest'
  $ '.result' .html data  # $.get \ajaxtest의 콜백이며, 콜백의 인자는 data
  processed <-! $.get 'ajaxprocess', data  # 콜백 내에서 `ajaxprocess`에 대한 처리를 또 호출
  $ '.result' .append processed # 여기는 콜백 내의 콜백이지만, 들여쓰기는 더 이상 없다.

alert \hi

위 코드는 다음과 같이 컴파일 된다.

$.get('ajaxtest', function(data){
  $('.result').html(data);
  $.get('ajaxprocess', data, function(processed){
    $('.result').append(processed);
  });
});
alert('hi');

LET/NEW

함수와 관련하여 let, new에 대한 표현을 짚고 마무리하도록 하겠다. let은 특정한 익명함수를 생성하는데, 해당 함수 내에서의 특정한 문맥을 생성해준다. 즉 let A = B { 블럭} 의 형태이며, 이 때 블럭 내에서 언급되는 A는 모두 B로 치환된다.

let $ = jQuery
  $.isArray []

위의 이 표현은 $jQuery가 되는 블럭 스코프를 생성한 후에 $.isArray []를 평가하였으므로 true가 된다. 이 때 외부 스코프에 $이 있더라도 여기서는 자체 스코프만을 참조할 것이다. 비슷하게 아래와 같은 코드도 작성할 수 있다.

x = let @ = a:1, b:2
  @b ^ 3
x #=> 8

new는 새로운 익명 컨스트럭터를 만들어서 즉시 호출하는 개념이다.

doc = new
  @name = \spot
  @mutt = true

# {name: 'spot', mutt: true}

보너스

다음은 nodejs를 통한 간단한 HTTP 서버의 기본 구현을 LS로 작성한 것이다. 콜백속에서 또 콜백을 전달하는 형태의 함수호출 패턴이 존재하고, 체이닝이 쓰인다. 이를 do와 백콜을 이용하여 깔끔하게 작성할 수 있다.

require! <[ fs http url ]>
const ROOTDIR = 'html/'

do
  (req, res) <-! htttp.create-server
  url-obj = url.parse req.url, yes no
  do 
    err, data <-! fs.read-file ROOTDIR + url-obj.pathname
    if err? 
      res.write-head 404
      res.end <| JSON.stringify err
    else
      res.write-head 200
      res.end data
.listen 8080

참고자료

  • LiveScript.net
  • HTTP 서버 구현: https://mylko72.gitbooks.io/node-js/content/chapter7/chapter7_4.html

  1. named function. 여기서는 function 키워드를 써서 정의한 함수를 말한다. 
  2. arguements에 대한 MDN 설명 참조. 

SCSS/SASS 문법 정리

SCSS는 기존 CSS 문법에 SASS문법을 섞은 것이며, SASS 컴파일러로 그대로 컴파일 될 수 있다. 이 글에서는  SCSS 문법을 기준으로 SASS의 각 기능을 사용하는 방법에 대해 설명하도록 하겠다.

기본문법 – 셀렉터 지정 및 속성 작성

SCSS의 기본문법은 기본적으로 CSS 기본 문법을 그대로 적용하고, 여기에 SASS 식의 치환가능한 요소들을 추가할 수 있다. 기본룰은 다음과 같다.

  1. 기본틀은 CSS 문법과 동일하다. 셀렉터를 쓰고 { .. } 블럭안에 속성:속성값; 의 형태로 속성들을 정의할 수 있다.
  2. nested block을 적용할 수 있다.
  3.  //을 이용해서 라인단위로 주석처리를 할 수 있다.

셀렉터 네스팅

특정 선택자 내에는 속성 정의만 들어오는 것이 아니라, nested된 속성 정의 블럭이 들어올 수 있다.

.entry-content {
  p { font-size: 9.814rem; }
}

// compiled to
.entry-content p {
  font-size: 9.814rem;
}

이런 형식의 정의 방법은 워드프레스 테마등 특정 클래스 내의 많은 요소들을 정의해야 할 때, 반복적으로 셀렉터를 중복으로 써야 하는 고통을 없애준다.

속성 네스팅

선택자가 네스팅되는 것과 유사하게, 특정한 family로 묶여있는 속성들도 네스팅이 가능하다. 예를 들어 font의 경우 font-family, font-size, font-weight 등이 주로 세트로 정의되는데, 다음과 같이 하나의 세트(?)인 속성들은 속성의 하위 사전 형태로 작성할 수 있다.

.entry-content {
  p {
    font: {
      family: "Noto Serif CJK KR", serif;
      size: 9.814rem;
      weight: 400;
    }
  }
}

상위요소 참조

&을 사용하면 현재 블럭이 적용되는 셀렉터를 참조한다. 정확하게는 참조가 아닌 치환이다.  특히 현재 속성을 설정중인 셀렉터에 의사셀렉터를 적용할 때 유용하다.

a {
  text-decoration: none
  &:hover { text-decroation: underline; }
}

위 선언은 아래와 같이 컴파일 된다.

a { text-decoration: none; }
a:hover { text-decoratino: underline; }

& 은 현재 셀렉터로 치환되기 때문에 다음과 같이 복합어로 이루어진 클래스들을 하나의 블럭으로 네스팅하여 묶을 때도 유용하게 쓰일 수 있다. 워드 프레스 테마의 경우를 예로 들면, 위젯들은 모두 widget-**** 의 식으로 클래스 이름을 갖는다.  이 들을 개별 셀렉터로 쓰지 않고 아래처럼 네스팅할 수 있다는 이야기다.

.widget {
   font-weight: 400;
   &-area { font-weight: 600; }
   &-top_posts { font-weight: 1000; }
}

위 예에서 &.widget 으로 치환되므로 컴파일된 결과는 아래와 같다.

.widget { font-weight: 400; }
.widget-area { font-weight: 600; }
.widget-top_posts { font-weight: 1000; }

변수와 연산자

색상이나 선 스타일, 폰트 패밀리등은 대체로 사이트 내에서 공통적으로 정의해놓은 값을 쓰는 경우가 많다. 이들을 매번 지정하지 않고 변수로 들어서 사용하면 변경 시점에 변수의 내용만 수정하여 모든 곳의 값을 공통적으로 바꿀 수 있을 것이다.

변수 타입

SASS에서 변수는 타입을 가지며, 각 타입에 대한 평가나 연산도 가능하다.

타입 설명
숫자값 숫자 리터럴로 쓰인 값은 숫자로 판단한다. 크기를 나타내는 단위가 붙은 경우도 숫자로 취급한다. 12px, 1.534rem
문자열 문자의 경우 기본적으로 따옴표 여부에 상관없이 문자열로 취급한다.
색상값 색상명이나 색상리터럴로 표기된 값은 색으로 인식한다. (blue, #aa33cc, rgba(255, 0, 0, 0.3))
불리언 true, false
null
리스트 리스트 내 원소는 동일 타입일 필요는 없으며, 괄호 속에 컴마나 공백으로 구분된 값들을 리스트로 본다.
괄호 속에서 : 으로 키 : 값을 구분하여 쓴다.

숫자값의 경우에는 사칙 연산 및 산술 연산 함수의 적용이 가능하다.

예외 : 나눗셈

나눗셈의 경우에는 조금 애매한 부분이 있는데, 그것은 CSS의 원래 문법에 나눗셈 비슷한 문법이 있기 때문이다.

 p { font: 10px/8px; }

이런식으로 쓰이는 경우에  SASS 컴파일러는 해당 표기는 연산이라 판단하지 않는다. 다만 수식을 괄호로 둘러싸게 되면 이는 강제로 연산으로 처리된다. 그외에도 덧셈등의 다른 연산자와 같이 쓰인 경우에도 나눗셈 연산으로 처리된다.

문자열의 치환 및 내삽(interpolation)

#{...} 을 사용하면 문자열 내에 표현식의 결과를 내삽하거나, 다른 변수의 내용으로 치환하는 것이 가능하다. 이는 속성값의 일부 혹은 전체 뿐만 아니라 속성명이나 셀렉터에 대해서도 적용 가능하다.

$foo: bar;
$fontsize: 12px;
$lineheight: 30p;

p {
  font: #{$fontsize}/#{$lineheight};
  &.#{$foo} { color: red; }
}

이 예제는 다음과 같이 컴파일 된다.

p { font: 12px/30px; }
p.bar { color: red; }

임포트

@import 지시어를 이용해서 다른 css 파일을 임포트할 수 있다. 사실 이 기능은 css의 원래 기능이다. 대신 css 파일이 아닌 scss, sass 파일을 임포트할 수 있다.

확장

확장은 이미 정의해둔 다른 셀렉터의 속성에 현재 셀렉터가 얹어가는 효과를 낼 수 있다.  따라서 특정한 클래스군에 대해서 베이스 클래스에서 공통 속성을 지정하여, 다른 클래스들이 베이스 클래스를 상속받는 효과를 낼 수 있다.  확장 문법은 @extend 최상위셀렉터의 형태로 사용한다.

// 베이스 클래스
.message {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.success {
  @extend .message;
  border-color: green;
}

.error {
  @extend .message;
  border-color: red;
}

공통 속성은 각각의 셀렉터 내로 포함되는 것이 아니라, 상속받는 셀렉터가 상위 셀렉터로 선언 부가 병합된다.  따라서 위 예제는 아래와 같이 컴파일 된다.

.message, .success, .error {
  border: 1px solid #cccccc;
  padding: 10px;
  color: #333;
}

.message { border-color: green; }
.error { border-color: red; }

믹스인

공통적으로 많이 쓰이는 CSS 선언값들을 묶어서 믹스인으로 만들어 재사용이 가능하게끔 할 수 있다. 변수는 단일 값을 담을 수 있는 것에 비해, 믹스인은 여러 속성의 정의 및 셀렉터에 대한 속성 전체등 블럭 단위로 재사용할 수 있다.

특별히 믹스인을 정의할 때에는 파라미터를 받을 수 있게끔 할 수 있기 때문에 단순 복붙이 아닌 파라미터 값에 따른 가변적 속성 집합을 만들어 유용하게 쓸 수 있다.

다음 예는 둥근 외곽선을 지정할 때 벤더별로 다른 접두어가 붙는 속성들을 매번 반복해서 쓰지 않도록 하는 테크닉이다.

@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
  -moz-border-radius: $radius;
  -ms-border-radius: $radius;
  border-radius: $radius;
}

.box { @include border-radius(10px); }

위 예에서 보듯 믹스인은 @mixin 키워드를 이용해서 이름과 인자를 선언한다. 인자가 필요없는 믹스인은 ($인자) 부분을 생략할 수 있다. 인자는 일반 변수처럼 정의한다.

믹스인을 사용할 때에는 @include 지시어를 사용한다.

믹스인의 인자값

믹스인의 인자는 선언하는 만큼 사용할 수 있다. 만약 인자값에 디폴트를 적용하고 싶다면, 변수 선언과 같은 문법으로 인자변수의 초기값을 설정해 줄 수 있다.

@mixin dashed-box($color, $width: 2px) { .. }

@include 구문에서 인자값은 선언된 순서대로 쓸 수 있으며, 보다 명확한 구분을 위해서 인자의 이름을 직접 기입할 수 있다. 인자의 각 이름을 명시한 경우에는 순서가 바뀌어도 상관없다.

.box { @incluxe dashed-box($width: 3px, $color: #eee) }

리스트 인자

인자명에 ... 을 붙이면 단일 값이 아닌 리스트로 인자를 받는 다는 의미이다. 이는 일련의 연속값을 속성으로 사용하는 경우에 활용할 수 있다.

이 문법은 파이썬의 *args, **kwds 등 시퀀스/맵 분해와 비슷하게 동작한다. 아래에서 살펴보겠지만 인자를 넣는 시점에도 같은 식으로 전달할 수 있다.

예를 들어 그림자 속성은 컴마로 구분된 리스트가 될 수 있다.

@mixin box-shadow($shadows...) {
  -moz-box-shadow: $shadows;
  -webkit-box-shadow: $shadows;
  box-shadow: $shadows;
}

.shadows { @include box-shadows(0px 4px 5px #666, 2px 6px 10px #999); }

... 표현은 리스트나, 맵을 개별 인자들로 분해해서 함수나 믹스인에 전달할 때 사용될 수 있다.

@mixin colors($text, $background, $border) {
  color: $text;
  background-color: $background;
  border-color: $border;
}

$values: #ff0000, #00ff00, #0000ff;
.primary { @include colors($values...); }

블럭을 믹스인에 넘기기

흔한 케이스는 아니지만, 블럭 자체를 믹스인에 넘겨줄 수 있다.  믹스인내에서 @content 지시어를 쓴 부분이 넘겨받은 블럭으로 치환된다.

@mixin code-inline {
  code {
    background-color: #cecece;
    padding: 2px;
    border-radius: @include border-raidus(4px);
    font-family: monospaces;
    @content
  }
}

p {
  @include code-inline {
    color: #33ccff;
    font-size: .8em;
  }
}

커스텀 함수

css 속성 정의의 모듈화와 재사용은 믹스인을 통해서 처리하면 된다. 함수는 어떤 값들을 사용해서 하나의 리턴값을 생성하는 용도로 사용하는 것이 좋다. 함수의 정의는 @function 지시어를 통해서 정의하며, 내부에서는 @return 지시어를 통해서 값을 내보낸다. 다음 예는 그리드 시스템에서 개별 셀과 여백의 크기를 통해서 n칸짜리 요소의 폭을 계산하는 함수이다.

$grid-width: 40px; 
$gutter-width: 10px; 
@function grid-width($n) {
   @return $n * $grid-width + ($n -1 ) * $gutter-width; 
} 
#sidebar { width: grid-width(5); } // 믹스인과 달리 @include를 쓰지 않는다.

함수 역시 믹스인과 마찬가지로 복수 인자 및 인자 분해 등을 적용해서 사용할 수 있다. 또한 SASS 내에서는 여러 기본 함수들이 내장되어 있는데, 이에 대해서는 분량 조절 관계로 별도로 다루도록 하겠다.

흐름제어

함수나 믹스인을 작성할 때 특정 조건에 따른 분기나, 조건 혹은 연속열 (리스트 나 맵)의 각 원소에 대해 반복하는 등 흐름 제어와 관련된 기능을 사용해야 할 필요가 있다. 이 때 흐름제어 지시어들을 사용할 수 있다.

분기분

분기구문은 @if 절을 이용하여 작성한다. @if 표현식 { ... } @else if 표현식 { ... } @else { ... } 식으로 연결되는 다중 분기를 만들 수 있다.

@mixin hcolor($n) {
  @if $n % 2 == 0 { color: white; }
  @else { color: blue; } 
}
.row-3 { @include hcolor(3); } 

@function text-color($brightness) {
  @if $brightness < 128 { @return #333; }
  @return #ccc; 
}
code { color: text-color(200);

반복문

반복문은 크게 3가지로 나뉜다.

  1. @for : n ~ m 까지의 숫자 범위에 대해 각 정수값에 대해 순회한다.
  2. @each : 주어진 리스트나 맵의 각 원소에 대해 순회한다.
  3. @while : 주어진 조건을 만족하는 동안 반복한다.

각각은 간단하게 예로 표현하겠다.

@for $i from 1 through 3 { // 1, 2, 3,에 대해 반복
  .time-#{$i} { width: 2em * $i; } 
} 

// 리스트 내 각 문자열 원소에 대해서... 
@each $animal in puma, sea-slug, egret, alamander {
  .#{$animal}-icon {
    background-image: url('/image/#{$animal}.png');   
  }
}

// 6, 4, 2번 아이템에 대해서 
$i : 6; 
@while $i > 0 {
  .item-#{$i} { width: 2em * $i }
  $i: $i - 2; 
}

여기서 소개하지 않은 몇 가지 기능들이 몇 가지 더 있는데 미디어 쿼리나 블럭 내에서 루트레벨 셀렉터 속성 지정하기, 플레이스 홀더를 사용한 지정문맥상속등이 있는데 흔히 쓰이는 기능은 아니어서 생략하도록 하겠다.

그외에 SASS에서 기본적으로 제공하는 내장함수들에 대해서는 별도로 살펴볼 필요가 있을 것이다. 특히 색상과 관련된 함수를 이용하면 별도의 그래픽 툴을 써서 색상값을 일일이 추출할 필요 없이 메인 색상으로부터 톤을 다양하게 변화시켜 사용하는데 활용할 수 있다.  그리고 리스트나 맵과 같은 자료 구조를 이용하면 더 많은 반복작업을 간단하게 처리하는데 도움이 될 수 있고, 이러한 기능들은 모두 기본 내장 함수에 의존하므로 그 때 그 때 찾아서 보도록 한다.