Topaz

토파즈 문법 기초

코드가 시가 되는 언어 토파즈의 문법을 마스터하세요. 현대적이고 표현력 풍부한 문법으로 아름다운 코드를 작성하는 방법을 배워보세요.

"코드가 시가 되는 언어" 토파즈의 문법은 최소한의 코드로 최대한의 표현력을 추구합니다. 이 가이드에서 토파즈만의 아름다운 문법을 마스터해보세요.

핵심 철학

"Write Less, Express More"

복잡한 로직도 간결하고 읽기 쉽게 표현할 수 있습니다.

글로벌 문법, 로컬 표현

  • 키워드: 영어로 통일 (function, let, match, case)
  • 식별자: 한글, 영어, 이모지 자유롭게 사용 가능

모든 것은 표현식

if, match, for, try 등 모든 구문이 값을 반환합니다.

기본 문법

변수 선언

// 불변 변수 (기본)
let 이름 = "김토파즈"
let 나이 = 25
let 활성화 = true

// 가변 변수
let mut 점수 = 85
점수 = 90  // 변경 가능

// 타입 명시 (선택적)
let 거리: float = 3.14
let 사용자들: Array<string> = ["김철수", "박영희"]

null 병합 할당 (??=)

대상 값이 None/null일 때만 초기화하는 문장 전용 연산자입니다.

let mut 이름: Option<string> = None
이름 ??= Some("guest")

함수 정의

// 기본 함수
function 인사하기(이름: string) -> string {
    "안녕하세요, {이름}님!"
}

// 기본값 매개변수
function 계산하기(x: int, y: int = 10) -> int {
    x + y
}

// 다중 반환값
function 좌표계산() -> (int, int) {
    (100, 200)
}

// 고차 함수
function 변환(데이터: Array<int>, 함수: (int) -> int) -> Array<int> {
    데이터.map(함수)
}

타입 위치에서 Topaz 공개 문서는 함수 타입을 (T) -> U 형태로 표기합니다.

조건문

// if 표현식 (값을 반환)
let 메시지 = if 나이 >= 20 {
    "성인입니다"
} else if 나이 >= 13 {
    "청소년입니다"
} else {
    "어린이입니다"
}

// 삼항 연산자 스타일
let 상태 = if 온라인 { "접속중" } else { "오프라인" }

패턴 매칭

// 기본 매칭
let 결과 = match 값 {
    case 1 => "하나"
    case 2 => "둘"
    case 3 => "셋"
    case _ => "기타"
}

// 범위 매칭
let 등급 = match 점수 {
    case 90..100 => "A"
    case 80..<90 => "B"
    case 70..<80 => "C"
    case _ => "F"
}

// 구조 매칭
let 할인율 = match 고객 {
    case { 등급: "VIP", 구매액 } if 구매액 > 1000000 => 0.3
    case { 등급: "VIP" } => 0.2
    case { 가입일 } if 오늘 - 가입일 > 365일 => 0.1
    case _ => 0.0
}

#### 리스트 패턴

```topaz
match xs {
  case [head, ..tail] => 사용(head, tail)
  case [..init, last] => 마지막사용(init, last)
  case [a, .., z] => 양끝사용(a, z)
  case [x, y, ..rest] if rest.length > 0 => 처리(x, y, rest)
}

엔드투엔드 예시(가드 + 중첩 리스트 패턴):

let 설명 = match 숫자들 {
  case [] => "빈 리스트"
  case [단일] => "하나"
  case [첫, .., 끝] if<=> "오름차순 경향"
  case [첫, ..중간, 끝] => "양끝 {첫}..{끝}, 중간={중간.length}"
}

파이프라인 연산

기본 파이프라인

// 데이터가 흐르는 듯한 표현
let 결과 = 원본데이터
    |> 정규화()
    |> 필터링(x => x > 0)
    |> 맵핑(x => x * 2)
    |> 정렬()

메서드 체이닝과 혼용

let 처리된문자열 = "hello world"
    .split(" ")
    |> 각각(단어 => 단어.첫글자대문자())
    .join(" ")
    |> 추가("!", _)
// 결과: "Hello World!"

파이프 슈거

v4

파이프 오른쪽에서 간결한 표기법을 사용할 수 있습니다.


  |> .length                  // 속성 슈거: (x => x.length)
  |> replace("foo", _, "bar") // 메서드 슈거: (x => replace("foo", x, "bar"))

// 여러 개의 _ 는 가장 가까운 호출에 왼쪽부터 순서대로 바인딩
|> format("id=", _, ":", _)

타입 시스템

타입 추론

// 타입이 자동으로 추론됩니다
let 숫자 = 42              // int
let 문자열 = "안녕"         // string
let 배열 = [1, 2, 3, 4]    // Array<int>
let 구조체 = {             // { 이름: string, 나이: int }
    이름: "김토파즈",
    나이: 25
}

리터럴 타입

// 정확한 값으로 타입 제한
type 신호등 = "빨강" | "노랑" | "초록"
type 주사위 = 1 | 2 | 3 | 4 | 5 | 6
type 상태코드 = 200 | 404 | 500

function 신호처리(색: 신호등) {
    match 색 {
        case "빨강" => 정지()
        case "노랑" => 주의()
        case "초록" => 출발()
        // 모든 경우를 처리했는지 컴파일러가 확인!
    }
}

유니온 타입

type 사용자입력 = string | int | null

function 처리(입력: 사용자입력) -> string {
    match 입력 {
        case 텍스트: string => "문자열: {텍스트}"
        case 숫자: int => "숫자: {숫자}"
        case null => "입력 없음"
    }
}

nullOption<T>는 관련되어 있지만 동일하지 않습니다. T | null 같은 nullable union에는 null을 사용하고, Option<T>에는 Some(...) / None을 사용하세요. ???.는 두 모델 모두에 동작합니다.

반복문

for 루프 (표현식)

// 범위 반복
for i in 1..10 {
    print("숫자: {i}")
}

// 배열 반복
for 항목 in ["사과", "바나나", "체리"] {
    print("과일: {항목}")
}

// 인덱스와 함께
for (인덱스, 값) in 데이터.enumerate() {
    print("{인덱스}: {값}")
}

// for도 값을 반환!
let 제곱수들 = for x in 1..5 { x * x }  // [1, 4, 9, 16, 25]

by를 사용한 범위 스텝

v4
// 증가 스텝
for i in 0..10 by 2 { print(i) }        // 0,2,4,6,8,10

// 감소 스텝 (음수 stride)
for i in 10..0 by -3 { print(i) }       // 10,7,4,1

// 날짜/시간 스텝 (라이브러리 기간 단위)
for day in 시작일..종료일 by 1.day { 일정.추가(day) }

while 루프

let mut 카운터 = 0
while 카운터 < 10 {
    print("카운터: {카운터}")
    카운터 = 카운터 + 1
}

컬렉션

배열

// 기본 배열
let 숫자들 = [1, 2, 3, 4, 5]
let 과일들 = ["사과", "바나나", "체리"]

// 배열 메서드
let 큰수들 = 숫자들.filter(x => x > 2)  // [3, 4, 5]
let 제곱들 = 숫자들.map(x => x * x)     // [1, 4, 9, 16, 25]
let 합계 = 숫자들.reduce(0, +)          // 15

멤버십 (in)

// 배열/리스트/집합
let ok1 = 3 in [1, 2, 3]
let ok2 = "a" in Set.of("a", "b")

// 맵 키
let hasId = "id" in 사용자맵.keys

// 범위
let inside = 5 in 1..10
let outside = 10 in 1..<10

객체/구조체

// 객체 리터럴
let 사용자 = {
    이름: "김토파즈",
    나이: 25,
    이메일: "topaz@example.com"
}

// 구조체 정의
struct 사용자 {
    이름: string,
    나이: int,
    이메일: string
}

// 구조체 인스턴스
let 새사용자 = 사용자 {
    이름: "박루비",
    나이: 30,
    이메일: "ruby@example.com"
}

레코드 업데이트 리터럴

v4

불변으로 얕은 복사를 한 뒤 선택한 필드만 갱신합니다.

let 사용자 = { 이름: "Alice", 나이: 20, 도시: "Seoul" }
let 갱신 = 사용자{ 나이: 사용자.나이 + 1, 도시: "Busan" }
// { 이름: "Alice", 나이: 21, 도시: "Busan" }

문자열 템플릿

기본 보간

let 이름 = "토파즈"
let 나이 = 25
let 인사 = "안녕하세요, {이름}님! 나이가 {나이}세이시군요."

태그드 템플릿

v4

표준 태그와 안전/메타 보존:

let 경로 = p"/home/{사용자}/docs/{파일명}"        // 경로 정규화
let 패턴 = r"^[a-z0-9_]+$"                         // 정규식 이스케이프 간소화
let 명령 = sh"grep {패턴} {파일}"                  // 안전 셸 템플릿(실행 정책 적용)

// SQL: 보간은 항상 파라미터 바인딩으로 처리 (직접 문자열 삽입 금지)
let 쿼리 = sql"SELECT * FROM users WHERE age > {나이} AND city = {도시}"

비동기 처리

자동 비동기

// I/O는 자동으로 비동기 처리되지만 동기처럼 작성!
function 사용자정보가져오기(id: int) -> 사용자 {
    let 기본정보 = API.사용자조회(id)      // 자동 비동기 처리
    let 프로필 = API.프로필조회(id)        // 자동 비동기 처리
    
    사용자 {
        기본정보: 기본정보,
        프로필: 프로필
    }
}

병렬 실행

// 여러 작업을 병렬로 실행
let 결과 = concurrent {
    날씨: 날씨API.현재날씨("서울"),
    환율: 환율API.달러환율(),
    뉴스: 뉴스API.헤드라인(5개)
}

에러 처리

Result 타입

function 안전한나누기(a: int, b: int) -> Result<int, string> {
    if b == 0 {
        Err("0으로 나눌 수 없습니다")
    } else {
        Ok(a / b)
    }
}

// ? 연산자로 간단하게
function 복잡한계산(x: int) -> Result<int, string> {
    let 결과1 = 안전한나누기(x, 2)?
    let 결과2 = 안전한나누기(결과1, 3)?
    Ok(결과2 * 10)
}

try 표현식

let 설정 = try {
    파일읽기("config.json")?
} else {
    // 기본값 반환
    { 테마: "다크", 언어: "한국어" }
}

고급 기능

부분 적용

let 더하기10 = 더하기(10, _)
let 결과 = [1, 2, 3].map(더하기10)  // [11, 12, 13]

옵션-안전 옵셔널 체이닝 (?.)

v4

좌변이 Option<T> 또는 T | null 같은 null 포함 유니온일 때만 사용하는 연산자입니다. 좌→우로 단락 평가하며 내부적으로 Option.map/flatMap으로 환원됩니다.

let 사용자: Option<{ 이름: string, 프로필: Option<{ 도시: string }> }> =
    Some({ 이름: "Ann", 프로필: Some({ 도시: "Seoul" }) })

let 이름옵션 = 사용자?.이름
let 도시옵션 = 사용자?.프로필?.도시

// ??와 조합해 기본값 제공
let 도시 = 사용자?.프로필?.도시 ?? "Unknown"

// 일반 객체 체이닝이 아닙니다 — Option 이 아닌 값에는 `.` 사용
let 일반 = { 이름: "Jo" }
let ok = 일반.이름

동등한 map/flatMap 표현:

// 사용자?.프로필?.도시
let 함수형 = 사용자
  |> Option.flatMap(_, u => u.프로필)
  |> Option.map(_, p => p.도시)

매크로

// 반복 실행 매크로
macro 반복실행(횟수: int) {
    for i in 1..횟수 {
        print("실행 {i}/{횟수}")
        yield  // 사용자 코드 실행
    }
}

반복실행(3) {
    중요한작업()
}

실용적 예제

웹 API 호출

let 사용자데이터 = fetch("https://api.example.com/users/1")
    |> json()
    |> (data => {
        이름: data.name,
        이메일: data.email,
        가입일: Date.parse(data.created_at)
    })

데이터 처리 파이프라인

let 분석결과 = CSV.읽기("sales.csv")
    |> 필터(행 =>.매출 > 1000000)
    |> 그룹화(행 =>.지역)
    |> 집계(그룹 => {
        지역: 그룹.키,
        총매출: 그룹..합계(행 =>.매출),
        평균매출: 그룹..평균(행 =>.매출)
    })
    |> 정렬(내림차순:=>.총매출)

다음 단계

이제 토파즈의 기본 문법을 마스터했습니다! 다음으로 학습할 내용:

토파즈와 함께 코딩이 시가 되는 경험을 계속해보세요!