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)를 실행하자
오류가 발생했다면 ('에러'):
'에러'의 '메시지'를 출력하자