하리Hari

8. 함수 (기능 만들기)

자주 쓰는 코드를 하나의 기능으로 묶어봐요.

자주 반복되는 코드들은 <이름>(꺾쇠 기호)을 사용해서 함수로 만들어두면 언제든 다시 꺼내서 실행할 수 있어요! 이름과 타입을 받아서 인사해주는 간단한 기능을 만들어볼까요?

<인사하기>를 만들자 ([문자열]인 '이름'):
    틀"{'이름'}님 안녕하세요!"를 출력하자

<인사하기>("손님")을 실행하자
<인사하기>("홍길동")을 실행하자

기능 안에서 계산을 하고, 그 결과를 바깥으로 돌려주게 만들 수도 있어요. 숫자 두 개를 더해서 결과를 돌려주는 기능을 만들어봐요. 돌려주는 값의 타입도 미리 정해줄 수 있답니다!

[숫자]를 돌려주는 <더하기>를 만들자 ([숫자]인 '가', [숫자]인 '나'):
    '결과값'을 [숫자]인 '가' + '나' 로 정하자
    '결과값'을 돌려주자

'총합'을 [숫자]인 <더하기>(10, 20)으로 정하자
틀"더하기 결과: {'총합'}"을 출력하자

돌려주는 값이 없는 함수라도 복잡한 과정을 하나로 묶어두면 코드가 훨씬 깔끔해진답니다!

<게임시작>을 만들자 ():
    "게임을 준비합니다..."를 출력하자
    "캐릭터를 생성합니다..."를 출력하자
    "시작!"을 출력하자

<게임시작>()을 실행하자

재료(인자)를 전달하는 방식: CBO (Call by Object)

함수에게 값(재료)을 전달할 때, 자료형에 따라 동작 방식이 조금 달라요. 이것을 보통 객체 참조에 의한 전달(Call By Object Reference) 이라고 부릅니다. 파이썬(Python)이나 자바스크립트(JavaScript)처럼 하리(Hari)에서도 동일하게 작동해요.

  • 숫자, 문자열, 논리 (원시 타입): 값을 복사해서 넘겨줍니다. 즉, 함수 안에서 값을 지지고 볶아도 바깥의 원래 변수에는 영향이 없어요!
  • 목록, 사전, 객체 (참조 타입): 원본의 “주소(참조)“를 넘겨줍니다. 함수 안에서 내용물을 수정하면 바깥의 원본도 같이 변해요!

직접 눈으로 확인해볼까요?

<숫자바꾸기>를 만들자 ([숫자]인 '값'):
    '값'에 10을 더하자

<사전바꾸기>를 만들자 ([(문자열, 아무거나)사전]인 '사전'):
    '사전'의 "상태"를 "바뀜"으로 정하자

<목록채우기>를 만들자 ([(숫자)목록]인 '목록'):
    '목록' 뒤에 99를 추가하자
    '목록'의 1번째를 7로 정하자

(참고) 1. 숫자를 넘겨볼게요
'내숫자'를 [숫자]인 5로 정하자
<숫자바꾸기>('내숫자')를 실행하자
틀"숫자는: {'내숫자'}"를 출력하자  (참고: 여전히 5가 출력됩니다)

(참고) 2. 사전을 넘겨볼게요
'내사전'을 [(문자열, 아무거나)사전]인 {"상태": "안바뀜"}으로 정하자
<사전바꾸기>('내사전')를 실행하자
틀"사전 상태는: {'내사전'의 "상태"}"를 출력하자  (참고: "바뀜"으로 변해있습니다!)

(참고) 3. 목록을 넘겨볼게요 - 추가하기, 꺼내기, 비우기, 자리 바꾸기 모두 원본에 적용돼요
'내목록'을 [(숫자)목록]인 [1, 2]로 정하자
<목록채우기>('내목록')를 실행하자
'내목록'을 출력하자  (참고: [7, 2, 99]로 변해있습니다!)

이렇게 목록이나 사전을 넘길 때는 원본이 변할 수 있다는 점을 꼭 기억해주세요! 목록을 다른 변수에 넣을 때도 같아요. '나'를 [(숫자)목록]인 '가'로 정하자로 만든 '나''가'같은 목록을 가리켜서, 한쪽에 추가하면 다른 쪽에서도 보여요.


돌려주는 타입은 적어도, 안 적어도 돼요

[숫자]를 돌려주는처럼 돌려주는 타입을 적으면 하리가 돌려준 값을 검사해요. 적지 않으면 아무 값이나 돌려줄 수 있어요. 아무것도 돌려주지 않고 끝나는 함수(비어있음)는 어떤 타입이어도 통과해요.

[숫자]를 돌려주는 <나이>를 만들자 ():
    "스물"을 돌려주자

<나이>()를 출력하자

실행해 보면 “TypeError: ‘나이’ 함수는 ‘숫자’ 타입을 돌려줘야 하는데 ‘문자열’ 값을 돌려줬어요.” 라는 에러가 떠요.


재료(인자)를 잘못 주면 어떻게 될까요?

함수를 호출할 때 필요한 인자(재료)를 안 주거나 너무 많이 주면 하리가 친절하게 막아줍니다.

[숫자]를 돌려주는 <두배로>를 만들자 ([숫자]인 '숫자'):
    ('숫자' * 2)를 돌려주자

(참고) 앗! 줘야 할 숫자를 까먹고 그냥 실행했어요!
'결과'를 [숫자]인 <두배로>()로 정하자

실행해 보면 “MissingArgumentError: ‘숫자’ 인자가 필요해요.” 라는 에러가 떠요.

반대로 재료를 너무 많이 주면 어떻게 될까요?

[숫자]를 돌려주는 <두배로>를 만들자 ([숫자]인 '숫자'):
    ('숫자' * 2)를 돌려주자

(참고) 숫자 하나만 필요한데 3개나 줘버렸어요!
'결과'를 [숫자]인 <두배로>(10, 20, 30)으로 정하자

이때는 “ArgumentError: 인자가 너무 많아요. 1개를 기대했는데 3개를 받았어요.” 라고 하면서 튕겨냅니다. 함수가 요구하는 정확한 재료 개수를 맞춰서 넣어주는 것이 중요해요!


인자에 기본값 정해 두기

인자 뒤에 = 값을 적으면, 호출할 때 그 인자를 생략해도 기본값이 채워져요.

<인사하기>를 만들자 ([문자열]인 '이름', [문자열]인 '말' = "안녕하세요"):
    틀"{'이름'}님, {'말'}!"을 출력하자

<인사하기>("하나")를 실행하자
<인사하기>("하나", "반가워요")를 실행하자

함수가 자기 자신을 부르기 (재귀)

함수 안에서 같은 함수를 다시 부를 수 있어요. 이것을 재귀라고 해요. 5의 팩토리얼(5 * 4 * 3 * 2 * 1)을 구해 볼까요?

[숫자]를 돌려주는 <팩토리얼>을 만들자 ([숫자]인 'n'):
    만약 ('n' <= 1) 라면:
        1을 돌려주자
    ('n' * <팩토리얼>('n' - 1))를 돌려주자

<팩토리얼>(5)를 출력하자

재귀에는 언제 멈출지가 꼭 있어야 해요. 위에서는 'n'이 1 이하이면 1을 돌려주고 멈춰요. 멈추는 곳이 없으면 호출이 끝없이 쌓이다가, 10000단계를 넘으면 하리가 RecursionError로 막아 줘요. 이 오류는 잡을 수도 있어요.

<무한>을 만들자 ('n'):
    <무한>('n' + 1)를 실행하자

일단 해보자:
    <무한>(1)를 실행하자
오류가 발생했다면 ('에러'):
    '에러'의 '메시지'를 출력하자
놀이터
결과
코드를 작성하고 '실행' 버튼을 눌러보세요.