파이썬은 처음이라 – 함수는 처음이라

함수는 일련의 동작을 수행하는 코드를 묶어서 재사용하기 쉽도록 호출가능한 단위로 만든 것을 말한다. 수학적인 관점에서 함수는 어떠한 입력이 주어졌을 때, 그 입력값을 사용하여 어떤 연산을 처리하고 다시 그 결과를 되돌려주는 계산상자에 비유되기도 한다. 파이썬의 함수는 이러한 두 특성을 모두 가지는 코드 덩어리이다.

함수의 수학적인 정의를 말로 풀어내지 않더라도 “계산상자”나 혹은 자판기 같은 것을 생각하면 함수가 어떤 역할을 하는지 쉽게 비유할 수 있다. 커피 자판기를 함수로 본다면 동전을 넣으면 커피가 나오는 함수에 비유할 수 있다. 물론 우리 모두는 커피 자판기 내부에 미리 넣어놓은 커피와 물 그리고 컵이 있고, 자판기에 동전을 넣으면 컵에 커피와 뜨거운 물이 담겨서 커피가 나온다는 사실은 알고 있다. 하지만 중요한 것은 동전을 넣으면 커피가 나온다는 사실, 즉 입출력에 대한 것이며, 함수에서 그 내부에 어떤 동작이 실제로 수행되는지는 사실 중요하지 않다.1

프로그램 역시 사용자로부터 정해진 형식의 데이터를 입력받아, 처리된 결과를 출력한다는 측면에서 하나의 함수라 볼 수 있다. 그리고 그 내부의 처리 과정에 있어서 입력값은 더 작은 단위의 함수에 의해서 중간값으로 변환되고, 다시 그 중간값이 또 다른 함수에 의해서 변환되는 과정을 거친 후 최종적으로 출력될 값이 된다. 2 즉 함수는 프로그래밍 코드를 재사용하는 단위인 동시에 프로그램을 구성하는 빌딩블럭(building block)으로도 볼 수 있다.

함수의 요소

사실 어떤 함수를 작성하는 것은 문법만 맞춘다면야 뭐 어떤 코드 블럭을 집어넣든 그것은 프로그래머의 마음이기 때문에 딱히 함수는 이렇게 정의해야 한다는 법칙 같은 것은 없다. 하지만 가능하다면 함수는 “어떤 일을 하는 단위”로 정의하고, 여기서 일은 앞서 설명한 바와 같이 입력을 받아 처리하고 출력값을 내놓는 범위로 정하면 된다. 따라서 함수의 정의는 함수가 갖추어야 할 요소들을 정의하는 것에서 출발한다.

  • 함수의 이름 : 함수의 이름은 여느 식별자와 마찬가지로 고유해야 한다.
  • 함수의 입력 : 함수의 입력은 함수가 실행될 때 전달받는 인자들이 된다. 인자는 하나 이상이거나, 경우에 따라서는 인자가 필요없는 경우도 있다.
  • 함수의 출력 : 함수가 어떤 값을 출력할지를 결정한다. 경우에 따라서 함수의 출력값이 없는 경우도 있을 수 있다. 이 경우에 함수는 암묵적으로 None을 리턴하게 된다.

함수 정의 문법

함수를 정의하는 문법은 다음과 같다.

def some_func( arg1, arg2 ):
^^^ ^^^^^^^^^ ^^^^^^^^^^^^ ^
 1    2         3          4
  5 블럭...
  6 return result
  1. 함수의 정의는 def 키워드로 시작한다.
  2. 함수의 이름을 선언하고
  3. 괄호 속에 함수가 받아들일 인자의 이름을 선언한다. 인자가 2개 이상인 경우 컴마로 구분하며, 인자가 필요없는 경우에는 빈 괄호를 사용한다.
  4. 선언부의 끝은 콜론으로 끝나며, 이는 그 다음줄 부터는 들여쓰기를 적용하는 블럭이란 의미이다.
  5. 함수가 실제로 처리할 코드 블럭을 작성한다.
  6. 함수의 끝은 return 문으로 끝난다. return 문은 함수가 리턴해야 할 값을 표현식으로 정의해준다. 리턴 값 없이 return 만 사용하면 return None으로 해석된다. 만약 return 문이 없는 함수는 함수의 마지막 줄에 암묵적으로 return None이 있는 것으로 간주한다.

간단한 함수를 정의하는 예를 몇가지 살펴보자.

## 두수의 합을 계산하는 함수
def add(x, y):
  return x + y

## 두수의 곱을 계산하는 함수
def mul(x, y):
  return x * y

## 정수 N을 입력 받아 1~N까지의 합을 계산하는 함수
## 단, N < 1 이면 0을 리턴한다. 
def accumlate(n):
  if n < 1:
    return 0 ## return 문을 만나면 함수의 실행은 여기서 종료된다.
  result = 0
  for i in range(n):
    result = reslut + i + 1
  return result

## 아래 함수는 메시지를 출력만 한다.
def print_tag(msg, tagname):
  print("<" + tagname + ">", msg, "</" + tagname + ">")
  ## 명시적인 리턴구문이 없으므로 여기까지 실행되면
  ## 함수의 실행이 종료된다. 

함수의 호출

함수의 내용을 실행하는 것을 함수를 호출한다고 표현한다. 함수의 호출은 함수의 이름 뒤에 괄호를 붙인다. 함수가 인자를 필요로 하는 경우에는 함수에 정의된 순서대로 인자를 콤마로 구분하여 넣어준다. 함수을 호출하는 문법은 그 자체로 하나의 표현식으로 취급되며, 이 표현식은 함수의 리턴값으로 평가된다. 우리는 이 글 이전에도 몇 가지 기본 함수를 사용하는 것을 예제를 통해 접해본 바 있다.

a = add(3, 4)
## a => 7
print(a)
## 7

## accumluate(10)은 그 자체로 결과값으로 평가되는 표현식이므로
## 다른 함수의 인자로 전달할 수 있다. 
print(accumlate(10))
## 55

print(int(input()) + 3)
## 입력된 숫자에 3을 더한 값을 출력한다. 

기본함수 input()은 키보드로부터 한줄의 문자열을 입력받는 함수이다. 따라서 input() 이라고 쓴 표현식은 키보드의 입력이 들어오게 되면 그 내용으로 구성된 문자열로 평가된다.

함수 호출과 흐름

파이썬 스크립트는 그 자체로 소스코드인 동시에 프로그램이기도 하다. 스크립트가 실행되면 소스코드의 맨 윗줄부터 파이썬 해석기에 의해서 실행되기 시작한다. 만약 어떠한 함수 호출도 사용하지 않는 프로그램을 작성했다면, 프로그램의 실행 방향은 코드의 위에서 아래로 흐르게 된다. 프로그램의 시작과 동시에 발생하는 실행 흐름을 메인 루틴(main routine)이라고 한다. 메인 루틴이 진행되는 과정에서 어떤 함수를 호출하게 되면 무슨 일이 생길까?

  1. 함수 호출 구문을 만나면, 현재 실행위치를 ‘어딘가’에 저장해두고 함수의 블럭 시작 위치로 실행 위치가 옮겨간다.
  2. 이 때, 전달된 인자값을 복사하여 가져가게 된다.
  3. 전달된 인자값은 함수 내에서 통용되는 변수가 되고, 이 값들을 이용해서 함수의 코드들이 실행된다.
  4. 리턴문을 만나거나 함수의 끝에 다다르면 1.에서 저장해두었던 위치로 돌아간다. 만약 리턴되는 값이 있다면 이 값을 가지고 가게된다.

즉, 함수를 호출하게 되면 메인 루틴이 잠시 중단되고 또 다른 별개의 실행 흐름이 시작된다. 이는 마치 고속도로의 1차선으로 달리다가, 함수를 호출하는 동안 2차선으로 차선을 변경한 후, 함수의 실행이 끝나면 다시 1차선으로 되돌아가는 것과 비슷하다고 하겠다. (물론 고속도로에서 차선을 바꾼다고, 원래 위치로 점프하는 것은 아니지만…) 만약 함수 내에서 다시 다른 함수를 호출한다면? 2차선에서 3차선으로, 3차선에서 4차선으로 계속해서 차선을 바꿔 “내려가게” 되고, 각 단계에서의 실행이 종료되면 다시 차선을 거슬러 올라가 1차선으로 돌아가게 된다. 따라서 메인 루틴을 중지하고 별개의 루틴으로 진입하게 된다는 점에서 함수의 실행 흐름을 서브 루틴(sub routine)이라고도 부른다.

메인루틴과 서브루틴은 절차지향적인 프로그래밍 관점에서의 비선형적인 실행 흐름을 이야기할 때 쓰는 표현이니, 그냥 그렇게 부르더라하는 정도로 이해하면 되겠다. 우리가 주목해야 할 점은 값 즉, 데이터이다. 입력 혹은 출력이 없는 몇몇 함수들을 예외적으로 둔다면, 데이터는 함수의 입력으로 들어가서, 함수의 내부에서 변환되어 출력으로 나오게 된다. 즉 함수는 그 외부에서 보았을 때 입력값을 변형하여 출력하는 장치로 볼 수 있다. 자판기라는 것을 전혀 본 적이 없는 사람의 입장에서 커피 자판기는 반짝거리는 쇠조각을 커피로 바꾸는 마법의 상자에 다름없듯이 말이다. 즉 “값을 조작하는 변환기”라는 관점에서 함수를 이해하고 있는 것이 앞으로 우리가 이야기하려는 관점에서는 매우 중요하다.

함수의 인자

함수의 인자를 정의하는 방법에 대해서 다시 생각해보자.

  1. 어떤 함수들은 인자를 받지 않은 경우가 있다.
  2. 어떤 함수들은 하나 혹은 그 이상의 고정된 인자를 받는다.
  3. 어떤 함수의 인자들은 있는 경우도 있고 없는 경우도 있다. (input(), print() 등)
  4. 어떤 함수들은 1개 이상의 정해지지 않은 개수의 인자를 받는다
  5. 어떤 함수들은 인자에 이름을 붙여야 하는 경우가 있다.

이중에서 함수의 인자가 고정된 경우는 앞서 소개한 문법을 사용해서 정의하는 것이 가능하다. 그렇다면 선택적 인자(있어도 되고 없어도 되는) 와 가변 인자(한 개 일수도, 여러 개 일 수도 있는)는 어떻게 정의할 수 있을까?

기본값을 갖는 인자

함수의 인자를 정의할 때, 디폴트 값을 정의할 수 있다. 인자에 디폴트 값을 정의하는 경우에는 호출하는 표현에서 해당 인자를 생략하면, 지정한 디폴트값을 사용한다.

def greet(name="unnamed"):
  print("hello, ", name)

greet('Tom')
# "hello, Tom"
greet()
# "hello, unnamed

위 함수에서와 같이 name 이라는 인자를 선언하면서 name="unnamed"라고 기본값을 지정해주었다. 이렇게 선언하면 해당 인자는 생략이 가능한 인자가 된다.  두 개 이상의 인자를 갖는 함수에서 일부 인자들만 기본값을 갖는다면, 기본값을 갖는 인자들을 항상 뒤쪽에 배치해야 한다. 왜냐하면 함수를 호출할 때, 인자값을 순서대로 넣기 때문이다.

## 동작하지 않는 예제!!
def some_func(a, b=1, c):
  return a + b + c

some_func(1, 2, 3) 
## a->1, b->2, c->3 임을 알 수 있다. 
some_func(1, 2)
## a->1 이지만 2는 b인가? c인가?

함수를 호출했을 때, 함수의 내부에서는 괄호안에 들어온 값들을 순서대로 매칭하려고 시도한다. 따라서 기본값이 없는 인자들을 구분할 수 있는 방법은 오로지 인자의 순서이다. 그렇기 때문에 인자 목록의 중간에는 디폴트 값을 갖는 인자를 넣을 수 없다. 파이썬에서는 이렇게 디폴트 값을 갖도록 선언한 인자를 ‘키워드 인자’라고 따로 구분해서 부른다. 그 이유는 다음의 가변인자에 대해 설명한 후에 풀어나가겠다.

가변 인자

두 개의 정수를 받아서 그 중에서 큰 값을 리턴하는 my_max()라는 함수를 정의한다고 생각해보자.

def my_max(a, b):
  if a > b:
    return a
  return b

함수 자체는 간단한데, 경우에 따라서는 3개의 값 중에서 가장 큰 값을 찾아야 하는 경우가 있을 것이다. 물론 그 때는 my_max(a, my_max(b, c))와 같은 식으로 b와 c중에서 큰 값을 찾고 그것을 다시 a와 비교해서 세 수 중의 최대값을 찾는 방법도 있을 것이다. 혹은 세 개의 수에 대해서 최대 값을 찾는 또 다른 함수를 정의해야 할 필요가 있을지도 모르겠다.

def my_max3(a, b, c):
  return my_max(a, my_max(b, c))

세 수 중에서 최대값을 구하는 구현에는 여러 가지가 있을 수 있다.

  1. 세 수 a, b, c 에 대해서 먼저 a > b  일 때, a  > c 이면 a가 최대값이고,  그렇지 않다면 c가 최대값이다. 다시 b >= a 일 때 b > c 이면 b 가 최대값이고 그렇지 않다면 c 가 최대값이다.
  2. 세 수 중에서 두 수의 최대값을 찾는다. 그리고 그 값과 나머지 한 값 중에서 최대값을 찾으면 그것이 세 수 중의 최대값이다.

위 두 명제는 세 수에 대해서 최대값을 찾는 방법을 설명한 글이다. 어떤 글이 더 간결하고 이해하기 쉬운가? 위 my_max3()은 두 번째 문장을 그래도 코드로 옮겨놓았으며, 그만큼 간결하고 실수를 통해서 버그가 발생할 여지도 줄였다. 이것이 함수로 함수를 만드는 관점이 가지는 힘이다.

그런데, 그러다보면 4 개, 5개, 6개의 수에 대해서 최대값을 찾아야 하는 경우도 빈번하게 발생할 수 있을 수 있고, 그 때마다 인자를 달리하는 다른 함수들을 매번 작성하기는 번거롭다. 파이썬에서는 이렇게 인자의 개수가 정해지지 않은 함수를 정의하는 방법이 있다.

def some_func(*args):  #1
  pass

def my_max_n(a, b, *cs):  #2
  ...

바로 인자의 이름 앞에 *3을 붙이는 것이다. 이렇게하면 some_func(1, 2), some_func(1, 2, 3), some_func(1, 2, 3, 4)와 같이 인자를 얼마든지 많이 넣을 수 있다. 그리고 각각의 인자는 함수의 내부에서 리스트와 비슷하게 args[0], args[1],.. 과 같은 식으로 참조할 수 있다.

두 번째 my_max_n(a, b, *cs) 의 의미는 a와 b는 반드시 필수적으로 넣어야 하는 인자이며, 그 이후 자리는 가변인자들로 넣어도 그만, 안넣어도 그만인 셈이다. (실제 my_max_n() 함수의 구현에 관해서는 튜플에 대한 내용을 배운 다음에 설명하는 것이 좋을 것 같다.)

언패킹

가변 인자는 선언하고자 하는 경우에 고정 인자와 키워드 인자(방금 말했던 디폴트 값이 있는 인자) 사이에 위치해야 한다. 키워드 인자에 대해서도 모든 기본값을 정의하기 어렵거나, 특정한 환경 설정과 관련된 함수의 경우에 인자가 수십개가 넘어가는 경우가 있어서 일일이 인자를 정의하기 힘든 경우가 있다. 이 때는 가변 키워드 인자를 정의할 수 있는데, **변수명으로 선언할 수 있다. 이렇게 선언된 가변 키워드 인자는 함수 내에서  나중에 배우게 될 사전으로 취급된다.

정리하자면 인자의 정의 순서는 고정인자 > 가변인자 > 키워드인자 > 가변키워드인자 의 순으로 정의해주면 된다.

보너스 – 반환값이 2개인 함수

C와 같은 언어를 먼저 접해본 경험이 있는 사람이라면 이 지점이 상당히 당황스러울 수 있는데, 파이썬에서 함수는 하나의 리턴값만을 반환하는 것이 아니라, 2개 혹은 그 이상의 값을 한꺼번에 반환하는 것이 가능하다. 바로 가변 인자에서 잠깐 언급한 튜플(tuple)을 사용하는 방법이다. 표준 내장함수 중에서 divmod()라는 함수가 있는데, 이 함수는 두 수를 받아서 나눈 몫과 그 때의 나머지를 계산해서 한 번에 리턴한다. 예를 들면 다음과 같은 식이다.

def divmod(x, y):
  return x // y, x % y

리턴해야 하는 값이 2개 이상이어야 하는 경우는 언뜻 생각하기에 엄청 예외적일 수 있다고 생각되겠지만, “그 이전에 항상 다른 방법으로 해결해왔기 때문에” 그렇게 느끼는 것일 뿐, 이 패턴이 유용한 경우는 생각보다 매우 많다.

이렇게 해서 기본적으로 함수를 정의하고 호출하는 방법에 대해서 살펴보았다. 앞으로 많은 예제들은 함수를 정의하고 함수와 함수를 연계하는 식으로 문제를 해결해나가는 방법을 소개할 것이기 때문에 함수와 관련된 문법은 자연스럽게 익숙해질 것으로 생각된다. 또한 그러한 접근 방식을 통해서 보다 분명하고 간결하게 문제를 해결하는 힘을 기를 수 있기를 기대해 본다.


  1. 이것은 일종의 추상화이다. 어떤 함수에 대해서 입력의 형식과 출력의 형식이 정해져 있다면, 실제 함수의 구현은 함수외부의 입장에서는 관심사가 아니다. 예를 들어 자연수 N을 입력으로 주면 1~N까지의 자연수의 합을 계산하는 함수가 있다고 가정해보자. 여기서 중요한 것은 10을 넣으면 55가 계산되어 나온다는 점이며, 그 내부의 구현이 루프를 돌면서 누적값을 더해나가든, 삼각수 공식을 사용하여 계산하든하는 점은 함수를 사용하는 입장에서는 몰라도 된다는 점이다. 이것은 반대로 함수를 구현하는 입장에서도 중요한데, 입력과 출력의 형식만 똑같이 유지한다면 함수 내부의 구현 코드를 어떻게 바꾸든 그것은 그 함수를 사용하는 부분의 전체 코드를 변경할 필요가 생기지 않는다는 점이다. 결국 함수에서 그 내부와 외부가 공유해야 하는 정보는 함수의 입력과 출력의 형식이다. 
  2. 수학에서 함수를 합성하여 제 3의 함수를 만들 수 있는 것처럼, 프로그램을 함수로 보는 이 관점에서 결국 프로그램은 프로그램 내부에 정의된 함수들을 정교하게 합성한 합성함수로 간주할 수 있다. 
  3. 이렇게 변수 이름앞에 *가 붙은 것을 언팩 연산자라고 하며, 이는 컴마로 쓰여진 일련의 값들을 튜플로 바인딩하는 연산자이다. 

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

[C] 함수로 전달된 포인터

포인터를 함수의 인자로 받는 경우, 함수내에서 원본을 변경하는가.

내용이 너무 두서 없어서 포스트 전체를 수정합니다.

처음 의문이 든 부분은, 문자열을 가리키는 포인터를 함수로 넘겨주고 문자열을 변형하면 포인터의 값이 변하는가?라는것이었는데. 이는 사실 그리 어려운 문제가 아니다. 포인터는 메모리의 주소를 가리키는 타입의 변수이고 이는 사실 unsigned int 나 unsinged int64 등의 정수형과 비슷하게 숫자를(메모리 번지도 결국 숫자값이므로) 담는 변수이다.

그리고 함수에 선언된 인자는 함수 내부에서만 사용하는 지역변수이고, 표준 타입의 변수를 함수에 전달하면 그 값이 인자로 선언한 변수에 들어가는데, 이건 그냥 변수의 값이 인자에도 들어가는 것이므로 값이 복사되는 것이다. 따라서,

  • 함수 외부에서 특정한 구조체나 배열, 문자열에 대한 포인터를 저장한 변수 A가 있고 이를 함수의 인자로 넘기면
  • 함수의 인자 B는 A와 동일한 값을 갖는다. 다만 값이 같을 뿐, B가 A를 가리키는 포인터가 될 수는 없다.
  • A가 포인터라면 B도 포인터이므로, 이 포인터가 가리키는 곳의 값을 조작할 수는 있다. 이러한 조작은 함수의 종료 후에도 변경을 유지하게 된다.
  • B는 A의 사본으로서, 함수의 실행 중에만 유지되고 함수 실행 종료시점에 파괴된다. A에 들어있는 값 자체는 아무런 영향을 받지 않는다.

로 정리할 수 있겠다. 땅땅땅.

[C] 함수 내에서의 메모리 할당과 해제

함수에서의 메모리 할당과 해제

C에서의 메모리 할당/해제의 개념은 설명은 간단한데 실제 적용시에는 무척이나 어렵다. 물론 뉴비시절에 책 보고 따라 코드를 써볼 때에는 이게 그다지 와 닿지 않는다. 곰곰히 생각해보니 그런 책의 예제들은 그냥 숫자값만 다루다보니 그런 것 같다.

위 글에서 이어서 함수 하나를 만들어보자. 이 함수는 문자열 상수를 받아서 이를 대문자로 변경한 문자열을 만들어준다. 문자열 상수는 변경할 수 없으니, 새로운 문자열을 생성해야 한다. 기본적인 아이디어는 다음과 같이 구현된다. [C] 함수 내에서의 메모리 할당과 해제 더보기