Mojo의 function system
Part 1
2.3 Return
2.3.1 기본 반환
def helloWorld() -> String:
return "Hello, World!"
- 기본적으로 값은 소유권이 이전된 상태로 호출자에게 반환
- 반환 타입으로의 암시적 변환이 가능
2.3.2 참조 반환(Returning a Reference)
참조 반환은 값의 복사본을 만들지 않고서 원본 데이터에 대한 접근을 제공하는 방법입니다
함수는 가변 또는 불변 참조를 반환할 수 있는데요 조금 더 들어가보죠!
- 불변 참조(Immutable Reference)
- 가변 참조(Mutable Reference)
이렇게 두가지가 있습니다.
2.3.2.1 불변 참조(Immutable Reference)
fn get_number(numbers: List[Int]) -> Int:
return numbers[0]
fn main():
var numbers = List[Int](0,0,0,0)
numbers[0] = 42
var num_ref = get_number(numbers)
print(num_ref) # 출력: 42
지금 이 코드를 보게 된다면 코드에서 get_number를 호출할 때, 그 함수 안에서는 numbers를 수정할 수 없습니다. 이게 불변 참조입니다.
2.3.2.2 가변 참조(Mutable Reference)
fn get_number(mut numbers: List[Int], to: Int) -> Int:
numbers[0] = to
return numbers[0]
fn main():
var numbers = List[Int](0,0,0,0)
numbers[0] = 42
var num_ref = get_number(numbers, 100)
print(num_ref) # 출력: 100
print(numbers[0]) # 출력: 100
이렇게 mut
키워드를 사용해서 가변 참조를 할 수 있게 합니다. 그러면 함수 안에서 바꾼 내용이 밖에서도 유지가 됩니다.
사용 목적을 좀 찾아봤더니?
- 불변 참조: 데이터를 안전하게 공유하고 읽을 때
- 가변 참조: 원본 데이터를 수정해야 할 때
이렇다고 합니다.
2.3.3 명명된 결과값(Named Results)
이동, 복사가 불가능한 값을 반환할 때 사용합니다
struct ImmovableObject:
var name: String
fn __init__(out self, owned name: String):
self.name = name^
def create_immovable_object(owned name: String, out obj: ImmovableObject):
obj = ImmovableObject(name^)
obj.name += "!"
# 요 obj는 반환됩니다. out으로 나가는건데요 저게 main의 my_obj에 들어갑니다.
def main():
my_obj = create_immovable_object("test!")
print(my_obj.name)
안되는 예시
def create_immovable_object2(owned name: String) -> ImmovableObject:
obj = ImmovableObject(name^)
obj.name += "!"
return obj^ # Error: 복사나 이동 안되기 때문에 오류남.
out
인자 규칙을 사용하여 초기화되지 않은 변수를 지정 * 함수는 하나의 out
인자만 가질 수 있음 * 명시적 return
문이 필요하지 않음2.4 매개변수(parameter) 및 인자(argument)
2.4.1 가변 인자
2.4.1.1 동종 가변 인자(Homogeneous Variadic Arguments)
같은 타입의 입력들을 이렇게 연속적으로 입력할 때 받는 것입니다. 파이썬을 해보셨다면 굉장히 익숙하실 겁니다.
fn sum(*values: Int) -> Int:
var sum: Int = 0
for value in values:
sum = sum+value
return sum
def main():
print(sum(1,2,3,4,5,6,67))
return
이렇게 사용도 가능하다! 생각보다 많이 쓰는 방법
tip아직까지는 모조가 전부 개발된 것도 아니여서,
Homogeneous Variadic Arguments
를 사용할 때, 아직 타입에 따라서 처리하는 방법이 다르고 합니다.Inside the function body, the variadic argument is available as an iterable list for ease of use. Currently there are some differences in handling the list depending on whether the arguments are register-passable types (such as Int) or memory-only types (such as String)
Register-passable types
은 iterate할 수 있다는데, 그니까 int같은 친구들이요. 그런데 String 이런 애들은 안된데요.근데 이런 차이점을 줄여갈 것이라고 써 놨으니 기대해 봅시다.
그러면 String은 뭐 어떻게 해야될까요?
def make_worldly(mut *strs: String):
for i in strs:
i[] += " world"
fn main():
var str1: String = "hello"
var str2: String = "hi"
try: make_worldly(str1, str2)
except e: pass
print(str1)
print(str2)
이렇게 사용이 가능합니다!
2.4.1.2 이질 가변 인자 (Heterogeneous Variadic Arguments)
이질 가변 인자… 어.. 이게 번역을 어떻게 해야 될까요..? 동질의 반대말이라서 그냥 이렇게 적는데
Heterogeneous Variadic Arguments
은 같은 타입이 아닌 애들도 같이 입력으로 받아버리는 겁니다. 그래서 타입 안전성을 챙깁니다.
그대신 여러 인자 타입들을 한번에 처리해되기 때문에 일반화하는 코드나 이런 것들이 요구됩니다.
독스에서는 특성과 매개변수가 필요하다고 합니다.
def count_many_things[*ArgTypes: Intable](*args: *ArgTypes):
기본적으로 생김새는 이렇게 생겼습니다.
여기서 두가지 중요한 것을 쪼개서 보도록 하죠
[*ArgTypes: Intable]
- 인자 리스트ArgTypes
는 타입들의 리스트를 나타냄Intable
은 정수로 변환 가능한 타입이라는게 제약조건
(*args: *ArgTypes)
- 인자 리스트- 익숙한
*args
문법을 사용 - 각 인자의 타입은
ArgTypes
리스트에 정의
- 익숙한
def count_many_things[*ArgTypes: Intable](*args: *ArgTypes):
var total = 0
@parameter
fn add[Type: Intable](value: Type):
total += Int(value)
args.each[add]()
return total
fn main() raises:
# 다양한 숫자 타입을 섞어서 호출할 수 있습니다
var result1 = count_many_things(5, 11.7, 12)
print(result1) # 출력: 28
# 단일 타입만 사용할 수도 있습니다
var result2 = count_many_things(1, 2, 3, 4)
print(result2) # 출력: 10
# Float 값들도 자동으로 Int로 변환됩니다
var result3 = count_many_things(1.1, 2.7, 3.2)
print(result3) # 출력: 7
이렇게 사용할 수 있구요.
여기서는 모조가 자동으로 타입을 인식해서 ArgTypes
리스트를 생성합니다. args
는 VariadicPack
으로 제공되어, each()
메서드로 순회할 수 있습니다
각 인자마다 add
함수가 호출되며, 적절한 타입과 값이 전달됩니다 예를 들어서 위의 코드에서 result1을 보도록 합시다.
첫 번째 호출: Type=Int, value=5
두 번째 호출: Type=Float64, value=11.7
세 번째 호출: Type=Int, value=12
이렇게 되겠네요!
근데 여기서 조금 나아가서 최적화를 해봅시다. 만약에 단일 인자로 들어오는 경우가 많다면, 아래 코드처럼 최적화가 가능합니다!
fn print_string(s: String):
print(s, end="")
fn print_many[T: Stringable, *Ts: Stringable](first: T, *rest: *Ts):
print_string(String(first))
@parameter
fn print_elt[T: Stringable](a: T):
print_string(" ")
print_string(String(a))
rest.each[print_elt]()
- 단일 인자 호출 시
VariadicPack
생성을 건너뛸 수 있음 - 첫 번째 인자는 직접 처리되어 더 효율적
- 나머지 인자들만
VariadicPack
으로 처리
어때요 굉장히 엄청나죠!?
2.4.2 가변 키워드 인자
fn print_nicely(**kwargs: Int) raises:
for key in kwargs.keys():
print(key[], "=", kwargs[key[]])
fn main() raises:
print_nicely(a=7, y=8)
- 가변 키워드 인자는 항상
owned
인자 규칙으로 취급 - 모든 가변 키워드 인자는 동일한 타입
- 인자 타입은
CollectionElement
trait를 준수
2.4.2.1 위치 전용 인자(Positional-only Arguments)
fn min(a: Int, b: Int, /) -> Int:
return a if a < b else b
이 /
는 위치 전용 인자이다. 라는 것을 나타냅니당.
fn min(a: Int, b: Int, /) -> Int:
return a if a < b else b
fn main() raises:
result = min(5, 3) # 정상 작동
result = min(a=5, b=3) # 컴파일 에러!
print(result)
그래서 이렇게 활용됩니다.
왜 사용할까?의미가 명확해짐 왜냐하면 min에서는 작은 값을 반환하는데 그 순서가 중요하지 않습니다. 그래서 그냥 ‘비교’ 한다는 것이 더 직관직이겠죠?
인터페이스 안전성 이거는 확실한 장점인데 뭐 입력 파라미터의 이름을 바꾸더라도 이후 다른 곳에서 오류가 안 터지겠죠? 아주 좋습니다.
2.4.2.2 키워드 전용 인자 (Keyword-only Arguments)
fn sort(*values: Float64, ascending: Bool = True):
fn sort(*values: Float64, ascending: Bool = True) -> List[Float64]:
var sorted_list = List[Float64]()
for i in values: sorted_list.append(i)
for i in range(len(sorted_list)):
for j in range(len(sorted_list) - 1 - i):
if ascending:
if sorted_list[j] > sorted_list[j + 1]:
sorted_list[j], sorted_list[j + 1] = sorted_list[j + 1], sorted_list[j]
else:
if sorted_list[j] < sorted_list[j + 1]:
sorted_list[j], sorted_list[j + 1] = sorted_list[j + 1], sorted_list[j]
return sorted_list
fn main() raises:
var sorted = sort(1.0, 2.0, 3.0, 4.0, 5.0, 67.0, 0.0, ascending=True)
for i in range(len(sorted)):
print(sorted[i])
ascending
이 없어도 True
로 들어가구요 직접 지정해서 False
로 바꿀 수 있어요.
sort(1.1, 2.2, 3.3, False) # 컴파일 에러!
이렇게 호출은 불가능합니다!
왜 사용할까?명확한 의도 표현 정렬로 예시를 든 것처럼 오름차순, 내림차순의 여부는 정렬에 있어서 굉장히 중요합니다. 이때 이렇게 명시하는 것이 코드 의도가 확 보이겠죠?
안전성과 유연성 나중에 새로운 키워드를 추가하더라도 기존 코드가 바뀌지 않습니다. 아주 좋죠?
여기까지 기본적인 코드에 대해서 알아봤구요. 담부터는 struct를 배우고 활용으로 pygame과 합께 적용시켜 보도록 하겠습니다!