순수 유니타리 고차 언어 (Purely Unitary Higher-Order Language)
기존 양자 프로그래밍 언어들은 고차 구조(함수를 인자로 전달하거나 반환하는 기능)를 고전적(Classical) 컴퓨터 영역에 한정하고 있다.반면 Granthi는 양자 프로그램 자체를 일급 객체(First-class values)로 취급하여, 양자 프로그램끼리 인자로 전달되거나 중첩(Superposition) 상태로 직접 합성될 수 있도록 설계되었다.
배선(Wiring) 중심의 컴파일 및 타입 시스템
Granthi의 타입 시스템을 통과한 모든 프로그램(함수 타입 포함)은 경계 인터페이스 상의 하나의 유니타리 연산(Unitary operator)을 나타낸다.컴파일러는 이를 물리적 큐비트 레이아웃 상의 열린 양자 회로 조각(Open quantum circuit fragment) 형태의 ‘배선(Wiring)’으로 구현한다.
관측 없는 중첩 제어 흐름 (Tag-preserving Routing)
제어 흐름 분기를 고전적인 관측(측정)에 의존하지 않고 태그 보존형 루팅 방식으로 처리한다.이 덕분에 제어 흐름 자체가 붕괴하지 않고 중첩 상태를 유지할 수 있다.
대표 활용 사례: 양자 스위치 (Quantum Switch)
두 양자 연산의 실행 순서 자체를 중첩 상태로 제어하는 Quantum Switch 구조를 정적 회로로 컴파일하여 지원한다.
Granthi는 OCaml 기반의 DSL(Domain Specific Language)로 구현되어 있으며, 중간 표현 단계(IR)를 거쳐 최종적으로 양자 회로 컴파일 라이브러리인 pytket을 통해 실행 가능한 양자 회로로 변환된다.
양자 프로그램도 ‘함수처럼’ 다룰 수 있다면? — Granthi 소개
최근 양자 하드웨어는 빠르게 발전하고 있지만, 양자 프로그램을 어떻게 구성할 것인가?에 대한 이론은 여전히 초기 단계에 머물러 있다. 기존의 대부분 양자 프로그래밍 언어(Quipper, Q# 등)는 하나의 공통된 구조를 따른다: 고차원 함수, 제어 추상화, 프로그램 생성기는 모두 고전적(classical) 호스트 언어가 담당하고, 실제 양자 레이어는 큐비트에 대한 1차원 연산만 노출하는 방식이다.
즉, “양자 프로그램 자체를 값으로 넘기거나 반환받는다”는 고전 프로그래밍의 기본적인 추상화가 양자 세계에서는 막혀 있었다. 이것이 단순한 구현의 불편함이 아니라, 양자역학적으로 볼 때 본질적 제약일까? 아니다. Granthi라는 새로운 언어는 이 틀을 깨고, 순수하게 유니터리한 세계에서도 고차원 프로그래밍이 가능함을 보여준다.
1. “프로그램 = 값”의 시대
Granthi(‘매듭(knot)’을 뜻하는 산스크리트어 단어에서 유래하여 시스템의 핵심인 양자 회로의 “배선(Wiring)”을 상징)의 가장 큰 도전은 이렇다: 양자 프로그램 자체도 1급 값(first-class value)이 될 수 있다.
기존 언어에서 f와 g라는 두 양자 게이트가 있을 때, 이 둘의 적용 순서를 제어 큐비트의 중첩 상태에 따라 달리 하고 싶다면 어떻게 해야 할까? 고전적 언어에서는 “프로그램이 회로를 조립하는 메타프로그램” 역할을 할 뿐, 실행 시점에 함수 자체가 중첩되지는 않는다.
Granthi에서는 함수 타입 A ⊸ B를 가진 값이 그대로 큐비트 레지스터처럼 취급된다. 함수를 인자로 받아서(ctrl f처럼 제어 버전을 만드는 고차 함수), 함수를 반환하고, 심지어 두 함수의 합성 순서 자체를 중첩 상태로 만들 수 있다. 이것이 바로 논문의 대표 예시인 양자 스위치(Quantum Switch)다.
2. 함수 적용은 ‘소비’가 아니라 ‘연결’
고차원 추상화를 양자 세계로 가져오는 데 가장 큰 걸림돌은 가역성(reversibility)이다. 고전적 함수 적용은 “인자를 먹고 결과를 뱉는” 과정처럼 보인다. 하지만 양자 세계에서는 정보가 소멸되면 안된다.
Granthi는 이 문제를 와이어링(wiring) 관점으로 해결한다. 함수는 검은 상자가 아니라, 명시적인 입출력 포트를 가진 회로 조각이다. 적용(application)은 포트를 연결하는 것이지, 값을 소비하는 것이 아니다. 커링(currying)은 포트를 외부로 다시 노출시키는 배선 재구성에 불과하다.
이 관점에서 eval 함수조차도 단순한 정체성 와이어(identity wire) 두 개로 환원된다. 모든 잘 타입된 프로그램은 자신의 경계 인터페이스에서 유니터리(unitary) 연산을 나타내며, 이는 1차원 데이터 타입뿐 아니라 고차 함수 타입에서도 성립한다.
3. 관측하지 않고 길을 나눈다: 태그 보존 라우팅
양자 프로그래밍에서 if문은 위험하다. 관측을 동반하는 고전적 분기는 중첩 상태를 붕괴시키기 때문이다. Granthi는 이를 태그 보존 라우팅(tag-preserving routing)으로 대체한다.
QBool = /b ⊕ /b는 양자 불리언이다. 여기서 ⊕은 고전적인 ‘둘 중 하나’가 아니라, 일관적 합(coherent sum)이다. case 표현은 두 갈래로 값을 보내지만, 어떤 갈래로 갔는지 태그를 결과 타입에 그대로 붙여 반환한다. 따라서 제어 큐비트는 여전히 중첩 상태를 유지하며, 나중에 간섭(interference)을 일으킬 수 있다.
이 구조 덕분에 Granthi는 “어떤 분기를 탔는지”를 기록하는 증인 와이어(witness wire)를 두고, 그에 위상(phase)을 걸어주는 독특한 양자 효과—제어 흐름 역사에 대한 간섭—까지 표현할 수 있다.
태그 보존 라우팅 — “택배 분류의 비밀”
고전 컴퓨터의 if문을 떠올려 보자. 조건을 확인하고, 한쪽만 실행하고 나머지는 버린다. 이건 양자 세계에서는 큰 문제이다. 그 이유는? “확인” 자체가 관측이 되어 중첩 상태를 깨뜨리기 때문이다.
Granthi의 case는 이렇게 생각하면 쉽다:
“택배를 두 갈래로 보내지만, 어디서 왔는지 스티커를 떼지 않고 그대로 붙여서 보냅니다.”
예를 들어 QBool(양자 불리언)이 |0⟩과 |1⟩의 중첩 상태라고 해보자. 고전적 if라면 “아 이건 0이네?” 하고 확인해서 한쪽만 택하고 다른 쪽은 버려야 하지만, Granthi의 case는 이렇게 한다:
0쪽 갈래: 패키지를 A 창고로 보내고, “0번 태그”를 붙임
1쪽 갈래: 패키지를 B 창고로 보내고, “1번 태그”를 붙임
그리고 나중에 두 창고에서 나온 결과를 다시 합칠 때, 태그를 그대로 유지한다. 즉, “이건 0에서 온 결과”라는 정보를 결과물에 담아서 돌려주는 거다.
이 덕분에 프로그램은 “어떤 길로 왔는지”를 잊어버리지 않으면서도, 중첩 상태를 붕괴시키지 않게 된다. 나중에 위상(phase)를 조절해서 두 갈래 결과를 간섭(interference)시킬 수도 있다.
4. OCaml로 쓰고, pytket으로 돌린다
Granthi는 이론에만 머물지 않는다. OCaml 기반의 임베디드 DSL로 작성된 프로그램은, 타입 시스템이 선형성(linearity)을 강제하여 각 자원이 정확히 한 번 사용되도록 보장한다. 이 프로그램은 바인더가 없는 핵심 중간 표현(Core IR)을 거쳐, 최종적으로 pytket을 통해 실행 가능한 양자 회로로 컴파일된다.
가장 인상적인 점은 컴파일러가 고차원 프로그램의 의미론적 유니터리를 물리적 큐비트 배치상의 정적인 회로로 정확히 구현해낸다. 예를 들어 양자 스위치는 8큐비트 회로로 컴파일되며, 프레드킨(Fredkin) 게이트를 이용해 함수 포트들의 연결 순서를 제어 큐비트에 따라 물리적으로 재배선한다.
OCaml로 쓰고, pytket으로 돌린다 —> “고급 설계도 → 현장 도면”
이건 마치 건축가가 편한 도구로 설계도를 그리면, 현장에서는 다른 도구로 정밀 도면을 만들어 시공하는 것과 같다.
OCaml — “설계사의 스마트펜”
프로그래머는 OCaml이라는 언어 안에 Granthi 문법을 녹여놓은 DSL(도메인 특화 언어)로 코드를 쓴다. 여기서 쓰는 문법은 우리가 아는 함수형 언어와 비슷하다:
함수를 인자로 넘길 수 있고 (ctrl f처럼)
let으로 변수를 묶고
case로 분기를 탈 수 있다
OCaml의 타입 시스템이 “이 큐비트는 정확히 한 번만 써야 해!”라는 선형성(linearity)을 컴파일 타임에 강제해준다. 실수로 큐비트를 복사하거나 버리면, OCaml 컴파일러가 그냥 에러를 버려버린다. 마치 설계도에서 벽을 두 번 쓰거나 없애버리면 CAD 프로그램이 “이건 구조적으로 불가능합니다”라고 막는 것처럼.
pytket — “현장의 드릴과 못”
OCaml로 쓴 고급 코드는 중간에 Core IR이라는 단순한 JSON 형태로 변환된다. 이건 사람이 읽기엔 지루하지만, 컴파일러가 다루기엔 딱 좋은 형태다.
그다음 Python으로 된 백엔드 컴파일러가 이를 받아서 pytket이라는 양자 회로 라이브러리가 이해할 수 있는 형태로 바꾼다. pytket은 실제 양자 컴퓨터(또는 시뮬레이터)에서 돌아갈 수 있는 게이트 배치를 만드는 거다.
핵심: 추상화가 어떻게 와이어가 되나?
OCaml에서는 “함수 f를 인자로 받아서 제어 버전을 만드는 고차 함수”를 마음껏 썼는데, pytket으로 나온 결과물은 그냥 물리적 큐비트들을 연결하는 와이어와 게이트라는 것.
마치 건축가가 “이 공간을 유동적으로 연결해주는 구조물”이라고 추상적으로 말했는데, 현장에서는 정확한 길이의 철근과 볼트 배치도가 나오는 것과 같다. Granthi 컴파일러는 함수를 포트(입출력 구멍)가 있는 회로 조각으로 보고, 함수 적용을 “이 포트를 저 포트에 꽂는 것”으로 번역한다. 그래서 고차원 프로그램이라도 결국 양자 회로의 물리적 배선으로 환원된다.
5. 의도적으로 비워둔 것들
Granthi는 모든 것을 하려 하지 않는다. 측정, 혼합 상태, 고전적 피드백, 재귀 타입은 모두 의도적으로 배제되었다. 이들은 각각 밀도 행렬, 채널, 무한 루프 등을 요구하며 “모든 프로그램은 유니터리다”라는 핵심 불변성을 깨뜨리기 때문이다.
대신 Granthi는 적응형(adaptive) 양자 언어 내부의 순수 유니터리 커널로서의 역할을 지향한다. 측정과 고전 제어는 외부 호스트가 담당하고, 커널 내부에서는 고차원 함수형 코드를 마음껏 인라인하고, 추상화하고, 정규화할 수 있다. 이것이 바로 Granthi가 제시하는 설계 패러다임이다.
마치며
Granthi는 단순히 “양자 컴퓨팅에 람다 계산을 덧붙인 것”이 아니다. 양자역학이 허용하는 고차원 구조를 프로그래밍 언어의 문법과 타입 시스템, 그리고 컴파일러로 완전히 실현한 최초의 시도 중 하나다. 프로그램 자체가 중첩되고, 함수의 적용 순서가 중첩되고, 제어 흐름의 역사가 간섭하는—그런 세상을 코딩할 수 있다는 것, 그것이 Granthi가 던지는 가장 흥미로운 질문이다.