Mojo 프로그래밍 언어 도큐먼트 한국어

Alpha, Orderly·2023년 8월 24일
0

mojo

목록 보기
1/1

서론

mojo 프로그래밍 언어 ( 이하 mojo ) 는 파이썬처럼 쓰기 쉬우나 C++와 러스트의 성능을 가지는 언어입니다.
더욱이, mojo는 파이썬 라이브러리 생태계 전체의 이점 또한 제공합니다.

내장 캐싱, 멀티스레딩, 클라우드 배포 등의 기능을 가지는 차세대 컴파일러를 통해 이들을 성취하였습니다. 더욱이, mojo의 자동튜닝 및 컴파일 메타프로그래밍 기능을 통해 다양한 장치에 적용 가능한 코드를 작성 가능케 합니다.

Mojo는 익숙하게 사용되던 파이썬 생태계의 이점을 그대로 사용할수 있으며, 파이썬의 수퍼셋 언어로서 기존의 동적 기능들에 시스템 프로그래밍을 위한 기초 요소를 추가하였습니다.
새로운 기초 요소들로 인해 Mojo 개발자들은 현재 C, C++, Rust, CUDA 및 다른 가속기를 필요로 하는 고성능 라이브러리를 빌드 할수 있게 됩니다.
최고의 동적 언어와 시스템 언어를 함께 가져옴으로서 우리는 여러 단계의 추상화에 잘 작동하는, 초보 프로그래머부터 수많은 가속기 케이스들에 잘 작동하는 언어를 목표로 합니다.

Mojo 컴파일러 사용하기

터미널에 아래 코드를 입력함으로 Mojo 컴파일러를 사용합니다.
파일의 확장자는 mojo 혹은 🔥 가 될수 있습니다.

mojo hello.mojo
# hello.mojo 를 컴파일 및 실행한다.

mojo 언어 기본

let, var 선언

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 : 웹 브라우저의 콘솔, 파이썬 콘솔과 같이 코드를 바로 실행, 결과 출력을 할수 있는 환경

struct type

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와 int의 차이

파이썬의 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 과 동일하게 동적으로 작동합니다.

fn 정의

def의 "엄격한 버전"에 해당합니다.
fn을 사용하는 대신 @strict def로도 사용이 가능합니다.
fn와 def는 서로 바꿔서 사용할수 있으며, 제공하는 기능에는 차이가 없습니다.
허나 fn은 def에 몇가지 제한점을 둡니다.

  1. 기본적으로 argument 값들은 함수 몸체에서 불변입니다.
  2. argument 값들의 타입을 지정해야 합니다, 또한 리턴 타입도 지정해야 합니다.
    • 아무것도 리턴하지 않을시 None을 리턴하는것으로 작성합니다.
  3. 지역 변수의 암시적 선언은 제한됩니다, 즉 모든 지역 변수가 선언되야 합니다.
  4. 예외 처리가 가능하나, fn에 raises 키워드가 필요합니다.

mojo에서 def와 fn은 얼마든지 섞어서 사용될수 있습니다.

__copyinit__ 과 __moveinit__

@value Decorator

@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

가 됩니다.

__copyinit__

var a = HeapArray(3, 1)
# var b = a  # HeapArray의 __copyinit__ 메소드가 실행됩니다.
# HeapArray에 __copyinit__이 정의되지 않았을시 에러가 납니다.
  • 깊은 복사
  • __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))
  • self에 값을 복사하는 예시

__moveinit__

  • 얕은 복사
var a = HeapArray(3, 1)
# var b = a^  # HeapArray의 __moveinit__ 메소드가 실행됩니다.
# HeapArray에 __moveinit__이 정의되지 않았을시 에러가 납니다.

argument 전달 관리와 메모리 ownership

python의 def는 argument 전달시 기본으로 레퍼런스입니다, 내부에서 변경한 값이 외부에도 적용됩니다.
mojo의 def는 argument 전달시 기본으로 값이 전달됩니다, 내부에서 값을 변경해도 외부에도 적용되지 않습니다.
mojo의 fn은 argument 전달시 불변 레퍼런스입니다.

borrowed - immutable argument

  • inout 대신 기본으로 적용.
  • C++ 에서 const& 로 값을 가져오는것과 동일하게 작동한다.
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)

inout - mutable argument

  • argument 앞에 inout을 적어 변경 가능한 레퍼런스로 argument를 받아올수 있다.
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의 값을 설정한다.

owned와 ^

  • owned는 값에 대한 ownership을 가져오고 싶을때 사용한다.
  • 뒤에 ^를 붙혀 사용한다.
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와 fn의 argument passing 비교

  • def는 기본적으로 Objecet을 받아온다고 가정한다.
  • inout 이나 owned가 없는 argument는 기본적으로 변경 가능한 var으로 전달된다.
    • __copyinit__ 이 필요하다!
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
    ...
  • 위 두 함수는 기본적으로 동일하다.

파이썬 통합

파이썬 모듈 가져오기

  • Python.import_module 을 사용한다.
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)

파이썬에서 mojo 타입

  • mojo의 원시 타입은 파이썬 오브젝트로 변환된다.
  • list, tuple, 정수, 실수, 불대수, 문자열을 지원한다.
  • 아직 dict를 지원하지 않아 아래와 같이 사용한다.
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")

로컬 파이썬 모듈 가져오기

  • py 파일을 가져오고 싶을때 사용한다.
  • 아래 코드는 mypython.py 를 가져오는 코드이다.
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)
profile
만능 컴덕후 겸 번지 팬

2개의 댓글

comment-user-thumbnail
2024년 5월 7일

잘 봤습니다

답글 달기
comment-user-thumbnail
2024년 5월 7일

잘 봤습니다

답글 달기