mojo 프로그래밍 언어 ( 이하 mojo ) 는 파이썬처럼 쓰기 쉬우나 C++와 러스트의 성능을 가지는 언어입니다.
더욱이, mojo는 파이썬 라이브러리 생태계 전체의 이점 또한 제공합니다.
내장 캐싱, 멀티스레딩, 클라우드 배포 등의 기능을 가지는 차세대 컴파일러를 통해 이들을 성취하였습니다. 더욱이, mojo의 자동튜닝 및 컴파일 메타프로그래밍 기능을 통해 다양한 장치에 적용 가능한 코드를 작성 가능케 합니다.
Mojo는 익숙하게 사용되던 파이썬 생태계의 이점을 그대로 사용할수 있으며, 파이썬의 수퍼셋 언어로서 기존의 동적 기능들에 시스템 프로그래밍을 위한 기초 요소를 추가하였습니다.
새로운 기초 요소들로 인해 Mojo 개발자들은 현재 C, C++, Rust, CUDA 및 다른 가속기를 필요로 하는 고성능 라이브러리를 빌드 할수 있게 됩니다.
최고의 동적 언어와 시스템 언어를 함께 가져옴으로서 우리는 여러 단계의 추상화에 잘 작동하는, 초보 프로그래머부터 수많은 가속기 케이스들에 잘 작동하는 언어를 목표로 합니다.
터미널에 아래 코드를 입력함으로 Mojo 컴파일러를 사용합니다.
파일의 확장자는 mojo 혹은 🔥 가 될수 있습니다.
mojo hello.mojo
# hello.mojo 를 컴파일 및 실행한다.
mojo의 def 안에는 파이썬과 동일하게 이름에 값을 암시적으로 대입해 함수 스코프 변수로 사용할수 있습니다.
이는 매우 동적인 코드 작성법이나 두가지 뛰어넘어야 할 요소가 있습니다.
1. 시스템 프로그래머들은 타입-안정성과 성능을 위해 값이 변경 가능하지 않게 선언 해야 할수 있습니다.
2. 변수 이름의 대입에서 타입이 잘못되면 에러를 받길 원할수 있습니다.
이들을 지원하기 위해, mojo는 let을 통해 불변하는, var를 통해 값이 변하는 변수를 선언할수 있습니다.
각 값은 변수 스코프의 원칙을 따릅니다. ( Lexical scoping and name shadowing )
def your_function(a, b):
let c = a
# c = b # c는 불변 변수이기에 이 코드는 에러가 발생합니다.
if c != b:
let d = b
print(d)
def your_function():
let x: Int = 42
let y: Float64 = 17.0
let z: Float32
if x != 0:
z = 1.0
else:
z = foo()
# var, let은 늦은 초기화를 사용할수 있다.
print(z)
def foo() -> Float32:
return 3.14
def 안에서 let과 var를 사용하는것은 완전히 선택적입니다.
fn 함수 안에서는 모든 변수를 let과 var로 선언해야 합니다.
또한 mojo를 REPL 환경에서 사용시, 최상위 변수는 def에 있는 변수와 같이 취급됩니다.
즉, 암시적 타입 선언을 사용할수 있습니다.
이는 python의 REPL 환경과 동일합니다.
REPL : 웹 브라우저의 콘솔, 파이썬 콘솔과 같이 코드를 바로 실행, 결과 출력을 할수 있는 환경
mojo는 MLIR과 LLVM에 기반하여 여러 프로그래밍 언어에서 제공하는 최신 컴파일러와 코드생성 시스템을 제공합니다.
이들은 데이터 편성에 대한 관리와 데이터 필드의 직접 접근 및 성능 향상을 제공합니다.
mojo는 struct 타입을 제공함으로서 고수준이며 안전한 추상화와 어떤 성능적 하락 없이 저수준 동작을 지원합니다.
struct는 파이썬의 class와 유사합니다, 둘 다 메소드, 필드, 연산자 오버로딩, 데코레이터를 제공합니다.
아래는 파이썬의 class와 다른 점입니다.
1. 파이썬 클래스는 런타임에 메소드 혹은 프로퍼티를 클래스에 추가 가능합니다.
2. mojo struct는 정적입니다, 컴파일 타임에 모든것이 결정됩니다. 이를 통해 유연성과 성능을 맞교환 하며, 안전하고 사용하기 쉽습니다. 이로 인해 모든 메소드는 fn으로 사용합니다.
3. mojo struct의 모든 인스턴스 프로퍼티는 var, let 으로 선언되어야 합니다.
4. mojo struct는 런타임에 변경될수 없습니다, del을 통해 메소드를 지우거나 변경할수 없습니다.
아래는 간단한 struct의 예시입니다.
struct MyPair:
var first: Int
var second: Int
# 정적이기에 def가 아닌 fn을 사용함
fn __init__(inout self, first: Int, second: Int):
# inout은 parameter를 수정할수 있도록 한다.
self.first = first
self.second = second
# 비교 연산자 오버로딩 예시
fn __lt__(self, rhs: MyPair) -> Bool:
# self는 수정 불가능 ( inout이 없다 )
return self.first < rhs.first or
(self.first == rhs.first and
self.second < rhs.second)
mojo의 표준 타입들은 struct를 통해 만들어 졌습니다. ( Int, Bool, String, Tuple, 등 )
파이썬의 int는 매우 큰 숫자를 다룰수 있고, 여러 추가 기능을 가집니다, 허나 이들은 성능 저하를 일으킵니다.
mojo의 Int는 간단하고 빠르게 동작하도록 설계되었습니다.
추가적으로 Int는 당신이 Mojo에서 생성할 다른 커스텀 데이터 타입들과 같은 네이밍 스타일을 가집니다.
파이썬의 유연한 타입과 더불어 엄격한 타입체크를 지원합니다.
이를 통해 예측 가능하고, 관리가 쉽고, 안전한 코드를 만들수 있습니다.
타입체크를 사용하는 방식중 하나는 struct를 사용하는 것입니다.
타입체크를 통해 타입이 정확하다는것을 알게 되면, 이를 통해 최적화를 하여 C의 argument passing 혹은 기타 저수준 급의 효율을 가지게 할수 있습니다.
파이썬과 동일하게 mojo 또한 argument의 데이터 타입을 지정하지 않고 동적으로 관리할수 있습니다.
이는 동적으로 데이터를 받아 동적으로 데이터를 관리하는 API에 유용합니다.
하지만 타입 안전성을 보장하기 위해 mojo는 함수 및 메소드의 오버로딩을 지원합니다.
이를 통해 동일한 이름에 다른 argument를 받는 여러개의 함수를 정의할수 있습니다.
struct Complex:
var re: Float32
var im: Float32
fn __init__(inout self, x: Float32):
"""Construct a complex number given a real number."""
self.re = x
self.im = 0.0
fn __init__(inout self, r: Float32, i: Float32):
"""Construct a complex number given its real and imaginary components."""
self.re = r
self.im = i
# 생성자 오버로딩 예시
mojo는 리턴 타입으로는 오버로팅을 지원하지 안ㄹ습니다.
argument에 타입을 따로 지정하지 않으면 python 과 동일하게 동적으로 작동합니다.
def의 "엄격한 버전"에 해당합니다.
fn을 사용하는 대신 @strict def로도 사용이 가능합니다.
fn와 def는 서로 바꿔서 사용할수 있으며, 제공하는 기능에는 차이가 없습니다.
허나 fn은 def에 몇가지 제한점을 둡니다.
mojo에서 def와 fn은 얼마든지 섞어서 사용될수 있습니다.
@value
struct MyPet:
var name: String
var age: Int
@value decorator는 타입의 필드를 보고 빠진 멤버를 생성합니다.
위 struct에서 @value로 인해 빠진 부분이 생성되어
struct MyPet:
var name: String
var age: Int
fn __init__(inout self, owned name: String, age: Int):
self.name = name^
self.age = age
fn __copyinit__(inout self, existing: Self):
self.name = existing.name
self.age = existing.age
fn __moveinit__(inout self, owned existing: Self):
self.name = existing.name^
self.age = existing.age
가 됩니다.
var a = HeapArray(3, 1)
# var b = a # HeapArray의 __copyinit__ 메소드가 실행됩니다.
# HeapArray에 __copyinit__이 정의되지 않았을시 에러가 납니다.
var data: Pointer[Int]
var size: Int
var cap: Int
fn __copyinit__(inout self, other: Self):
self.cap = other.cap
self.size = other.size
self.data = Pointer[Int].alloc(self.cap)
for i in range(self.size):
self.data.store(i, other.data.load(i))
var a = HeapArray(3, 1)
# var b = a^ # HeapArray의 __moveinit__ 메소드가 실행됩니다.
# HeapArray에 __moveinit__이 정의되지 않았을시 에러가 납니다.
python의 def는 argument 전달시 기본으로 레퍼런스입니다, 내부에서 변경한 값이 외부에도 적용됩니다.
mojo의 def는 argument 전달시 기본으로 값이 전달됩니다, 내부에서 값을 변경해도 외부에도 적용되지 않습니다.
mojo의 fn은 argument 전달시 불변 레퍼런스입니다.
struct SomethingBig:
var id_number: Int
var huge: HeapArray
fn __init__(inout self, id: Int):
self.huge = HeapArray(1000, 0)
self.id_number = id
# inout을 통해 변경 가능한 reference를 argument 로 받아온다.
fn set_id(inout self, number: Int):
self.id_number = number
# borrowed가 적혀있는것과 동일하게 취급된다. ( 불변 )
fn print_id(self): # fn print_id(borrowed self): 와 동일.
print(self.id_number)
struct MyInt:
var value: Int
fn __init__(inout self, v: Int):
self.value = v
fn __copyinit__(inout self, other: MyInt):
self.value = other.value
# __copyinit__은 var b = a 의 b에서 실행되어, b의 값을 설정한다.
fn take_ptr(owned p: UniquePointer):
print("take_ptr")
print(p.ptr)
fn use_ptr(borrowed p: UniquePointer):
print("use_ptr")
print(p.ptr)
fn work_with_unique_ptrs():
let p = UniquePointer(100)
use_ptr(p)
take_ptr(p^) # p^ 와 같이 사용해 ownership을 전달한다.
# p의 ownership이 use_ptr로 전달되었고, 함수가 종료되어 p또한 destroy되어 사용할수 없다.
# use_ptr(p) 시 오류가 발생한다.
def example(inout a: Int, b: Int, c):
...
fn example(inout a: Int, b_in: Int, c_in: Object):
var b = b_in
var c = c_in
...
from PythonInterface import Python
# This is equivalent to Python's `import numpy as np`
let np = Python.import_module("numpy")
# Now use numpy as if writing in Python
array = np.array([1, 2, 3])
print(array)
from PythonInterface import Python
from PythonObject import PythonObject
from IO import print
from Range import range
var dictionary = Python.dict()
dictionary["fruit"] = "apple"
dictionary["starch"] = "potato"
var keys: PythonObject = ["fruit", "starch", "protein"]
var N: Int = keys.__len__().__index__()
print(N, "items")
for i in range(N):
if Python.is_type(dictionary.get(keys[i]), Python.none()):
print(keys[i], "is not in dictionary")
else:
print(keys[i], "is included")
from PythonInterface import Python
Python.add_to_path("path/to/module")
let mypython = Python.import_module("mypython")
let c = mypython.my_algorithm(2, 3)
print(c)
잘 봤습니다