글을 시작하기에 앞서 해당 시리즈는 Allen Downey, Ben Lauwens의 저서인 Think Julia: How to Think Like a Computer Scientist 를 바탕으로 작성된 글임을 알려드립니다.
이 포스트는 The Goodies: Base and Standard Library를 한글로 요약 정리한 글입니다.
베이스 및 표준 라이브러리 (Base and Standard Library)
줄리아는 기본 배터리가 포함되어 있다. 즉, 베이스(base) 모듈에는 가장 유용한 함수들, 데이터 타입 및 매크로가 포함된다. 이들은 모두 줄리아에서 바로 사용할 수 있다.
또한 줄리아는 표준 라이브러리에 많은 특수 모듈을 제공한다. 이를 사용하기 위해서는 먼저 라이브러리를 가져와야 한다.
import Module
은 모듈을 가져오며,Module.fn(x)
은 함수fn
을 호출한다.using Module
은 모든 모듈 함수, 데이터 타입, 매크로를 가져온다.
이 장은 공식적인 줄리아 문서를 대신하지는 않으며, 완벽한 것이 아니라 가능한 것에 대한 예시들을 제시할 것이다. 해당 예시에는 다른 곳에서 이미 소개된 함수들은 포함되지 않는다. 완벽한 전체 개요는 이 링크에서 찾을 수 있다.
성능 측정 (Measuring Performance)
우리는 몇몇 알고리즘이 다른 알고리즘보다 성능이 우수하다는 것을 알았다. 실제로 메모 파트에 나온 fibonnaci
는 연습해보기의 fib
보다 훨씬 빠르다. @time
을 사용하면 성능 차이를 숫자로 표현할 수 있다.
1 | julia> fib(1) |
@time
은 함수를 실행하는 데 걸린 시간, 할당 횟수 및 결과를 반환하기 전에 할당된 메모리를 출력한다. 메모 파트에서의 fibonacci
는 훨씬 빠르지만 더 많은 메모리를 차지한다.
세상에 공짜는 없다!
TIP
줄리아의 함수는 처음 실행될 때 컴파일된다. 따라서 두 알고리즘을 비교하려면 컴파일할 함수로 구현해야 하며, 처음 호출할 때는 성능 측정을 하면 안된다. 그렇지 않다면 컴파일 시간도 측정된다.
패키지 BenchmarkTools
은 올바른 방법으로 벤치마킹하는 매크로 @btime
을 제공한다.
모음 및 데이터 구조 (Collections and Data Structures)
딕셔너리 뺌셈 파트에서 우리는 딕셔너리를 사용하여 문서에는 있지만 단어 배열에는 없는 단어를 찾았다. 해당 함수는 문서의 단어를 키로 포함한 d1
과 단어 배열을 포함한 d2
를 가져간다. 그후 d2
에 없는 d1
의 키를 포함한 딕셔너리를 반환한다.
1 | function subtract(d1, d2) |
위의 딕셔너리에서 값은 사용하지 않기 때문에 nothing
이먀, 결과적으로 저장 공간이 낭비된 것이다.
줄리아는 마치 딕셔너리에서 키만 수집한 모음과 같은 셋(set)
이라고 불리는 다른 내장 데이터 타입을 제공한다. 셋에 요소를 추가하는 것은 빠르다. 그리고 셋은 일반적인 작동을 계산하는 함수와 연산자를 제공한다.
예를 들어 셋 뺄셈은 setdiff()
라는 함수를 사용하면 가능하다. 그래서 아래의 코드처럼 subtract()
을 재작성한다.
1 | function subtract(d1, d2) |
결과는 딕셔너리를 대신한 셋 형태이다.
이 책의 몇 가지 예제는 셋을 사용하여 간결하고 효율적으로 실행할 수 있다. 다음은 딕셔너리를 사용하여 중복을 해결하는 솔루션이다.
1 | function hasduplicates(t) |
요소가 처음에 나타날 때는 딕셔너리에 추가된다. 만약 동일한 요소가 다시 나타나면 이 함수는 true
을 반환한다.
셋을 사용하면 우리는 같은 함수를 아래와 같이 작성할 수 있다.
1 | function hasduplicates(t) |
요소는 오직 셋에서 한번만 나타날 수 있기 때문에 t
의 요소가 한번 이상으로 나타나면 셋은 t
보다 더 작아질 것이다. 만약 중복이 없다면 셋은 t
와 똑같은 사이즈일 것이다.
또한 워드 플레이 예제에서도 셋을 사용할 수 있다. 예를 들어 루프가 포함된 useonly
의 버전을 확인해보자.
1 | function usesonly(word, available) |
useonly
는 word
에 있는 모든 알파벳들이 available
에 속해 있는지를 확인한다. 위의 함수를 아래와 같이 재작성할 수 있다.
1 | function usesonly(word, available) |
⊆ (\subseteq TAB)
연산자는 두 개의 word
의 단어가 available
와 같은지의 가능성을 포함하여 한 셋이 서브셋인지를 확인한다.
수학 (Mathematics)
복소수(Complex numbers)는 줄리아에서 제공된다. 글로벌 상수 im
은 복소수 $i$이며, $-1$의 제곱근을 나타낸 것이다.
이제는 오일러의 등식(Euler’s identity)을 확인할 수 있다.
1 | julia> ℯ^(im*π)+1 |
기호 ℯ (\euler TAB)
는 자연로그의 기초이다.
이제는 삼각함수의 식을 작성해보자.
우리는 x에 다른 값을 넣어서 위의 방정식을 테스트할 수 있다.
1 | julia> x = 0:0.1:2π |
위의 코드는 점 연산자의 다른 예시이다. 줄리아는 또한 숫자가 2π
와 같은 계수로서 병치되는 것을 허용한다.
문자열
문자열 파트와 워드 플레이 파트에서는 우리는 문자열 객체에서 몇 가지 기본 검색을 진행하였다. 그러나 줄리아는 ‘Perl 호환 정규식(PCRE)’을 처리할 수 있으므로 문자열 객체에서 복잡한 채턴을 쉽게 찾을 수 있다.
1 | function usesonly(word, available) |
정규식(regex)은 available
문자열에 없는 문자를 찾고, occursin
은 패턴이 word
에 있는 경우 true
를 반환한다.
1 | julia> usesonly("banana", "abn") |
정규식들은 r
을 붙이면 일반적이지 않은 문자열로도 구성될 수 있다.
1 | julia> match(r"[^abn]", "banana") |
위의 경우에는 문자열 보간이 허용되지 않는다. match()
는 패턴을 찾지 못하면 아무것도 반환하지 않으며, 패턴을 찾으면 ‘regexmatch’ 객체를 반환한다.
정규식 객체에서 다음 정보를 추출할 수 있다.
- 매치된 전체 서브문자열:
m.match
- 캡처된 서브문자열을 문자 배열화:
m.captures
- 전체 매치가 시작되는 오프셋:
m.offset
- 캡쳐된 서브문자열 오프셋의 배열:
m.offsets
1 | julia> m.match |
정규식들은 매우 강력하며, PERL 페이지에서 다양한 세부 정보들을 제공한다.
배열
배열 파트에서는 배열 객체를 인덱스를 가진 1차원 컨테이너를 사용하여 해당 요소를 처리하였다. 하지만 줄리아에서 배열은 다차원 컬렉션이다.
2 x 3 제로 행렬을 만들어보자.
1 | julia> z = zeros(Float64, 2, 3) |
이 매트릭스의 데이터 타입은 부동소수점으로 구성된 2차원 배열이다.size()
는 각 차원을 나타내는 숫자를 요소로 가진 튜플을 반환한다.
1 | julia> size(z) |
ones()
는 인수로 데이터 타입과 숫자를 받아 매트릭스를 생성한다.
1 | julia> s = ones(String, 1, 3) |
문자열 유닛 요소는 빈 문자열을 나타낸다.
WARNING
s
는 1차원 배열이 아니다.
1 | julia> s == ["", "", ""] |
s
는 행(row) 매트릭스이며, [“”, “”, “”]는 열(column) 매트릭스이다.
매트릭스는 요소를 행으로 분리하기 위해 ;
을 구분자로 사용한다. 행을 직접 분리하는 코드는 아래와 같다.
1 | julia> a = [1 2 3; 4 5 6] |
매트릭스 이름 뒤 대괄호를 사용하여 개별 요소들을 직접 입력할 수 있다.
1 | julia> z[1,2] = 1 |
또한 각 차원의 슬라이스를 사용하여 요소의 하위 그룹을 선택할 수 있다.
1 | julia> u = z[:,2:end] |
.
연산자는 모든 차원에 적용된다.
1 | julia> ℯ.^(im*u) |
인터페이스
줄리아는 비공식 인터페이스를 지정하여 동작, 즉 특정 목표를 가진 메서드를 정의한다. 데이터 타입에 이런 메서드를 확장하면 해당 데이터 타입의 객체를 사용하여 동작을 빌드할 수 있다.
오리처럼 보이고 오리처럼 수영하고 오리처럼 울면 아마 그것은 오리일 것이다.
연습해보기 파트에서 우리는 피보나치 수열의 $n$번째 요소를 반환하는 fib()
를 구현하였다.
위의 함수는 컬렉션의 값을 반복하는 것이 인터페이스이다. 그러므로 피보나치 수열을 느리게 반환하는 이터레이터를 만들어보자.
1 | struct Fibonacci{T<:Real} end |
위의 코드는 외부 생성자와 iterate
메서드를 사용하여 Fibonnaci
필드 없이 파라메트릭 데이터 타입을 구현하였다. 첫 번째는 이터레이터를 초기화하기 위해 호출되며, 첫 번째 값 0과 상태(state)를 반환한다. 위 사례에서 상태는 두 번째 값인 1과 세 번째 값 1을 포함한 튜플이다.
두 번째는 피보나치 수열의 다음 값을 얻기 위해 호출되고 첫 번째 요소는 다음 값, 두 번째 요소는 두 개의 다음 값을 가진 튜플을 반환한다.
이제는 Fibonnaci
를 for
루프로 나타낼 수 있다.
1 | julia> for e in Fibonacci(Int64) |
이것은 마술처럼 보일 수 있지만, 원리는 간단하다. 줄리아에서 for
루프는 아래와 같다.
1 | for i in iter |
위의 루프의 프로세스를 풀어서 설명한다면 아래 코드와 같다.
1 | next = iterate(iter) |
이것은 잘 정의된 인터페이스가 어떻게 그에 맞는 함수들을 구현할 수 있게 허용하는지 보여주는 좋은 예시이다.
대화식 유틸리티 (Interactive Utilities)
우리는 이미 InteractiveUtils
모듈을 디버깅 파트에서 보았다. @which
매크로는 빙산의 일각에 불과하다.
줄리아 코드는 LLVM
라이브러리에 의해 여러 단계로 기계코드화 된다. 각 단계의 결과들을 직접 시각화할 수 있다.
아래의 간단한 예제를 살펴보자.
1 | function squaresum(a::Float64, b::Float64) |
첫 번째 단계는 ‘낮은 수준의 코드(lowered code)’를 보는 것이다.
1 | julia> using InteractiveUtils |
@code_lowered
매크로는 컴파일러가 일반화한 최적화 코드의 중간 단계를 배열로 반환한다.
다음 단계는 데이터 타입 정보를 추가하는 것이다.
1 | julia> 3.0, 4.0) squaresum( |
위의 결과에서 중간 결과의 데이터 타입과 정확하게 유추된 반환 값을 확인할 수 있다.
이 코드의 표현들을 LLVM
코드로 변환한다.
1 | julia> 3.0, 4.0) squaresum( |
그러면 마지막으로 기계 코드가 생성된다.
1 | julia> 3.0, 4.0) squaresum( |
디버깅
Logging
매크로는 프린트 구문의 스캐폴딩을 대체할 수 있다.
1 | julia> "Abandon printf debugging, all ye who enter here!" |
소스(source)에서 디버그 구문을 제거할 필요는 없다. 예를 들어 위의 @warn
과 달리 아래의 코드는 기본적으로 출력이 생성되지 않는다. 이 경우 ‘디버그 로깅( debug logging)’을 사용하지 않으면 sum(rand(100))
은 평가되지 않는다.
로깅 레벨은 환경 변수인 JULIA_DEBUG:
로 선택할 수 있다.
1 | $ JULIA_DEBUG=all julia -e ' "The sum of some values $(sum(rand(100)))"' |
여기서는 모든 디버그 정보를 얻기 위해서 모두 사용햇지만 특정 파일 또는 모듈에 대한 출력만 생성하도록 선택할 수도 있다.