Julia의 함수 사용팁

연산자의 함수적 표기

Julia의 연산자는 기본적으로 함수이며, 함수 호출 표기와 같은 방식으로 호출하는 것이 가능합니다. 또한 그 자체로 함수이기 때문에 filter(), map() 과 같이 함수를 인자로 받는 함수에도 연산자를 그대로 적용하는 것이 가능합니다. 특히 + 연산자는 sum() 함수와 같이 여러 인자를 받아 인자들의 합을 구할 수 있습니다.

2 + 3
# = 5

+(2, 3)
# = 5

+(2, 3, 4)
# = 9

>(3, 2)
# = true

이 때, 비교 연산자들은 첫번째 인자만 전달된 경우에는 부분 적용된 함수를 생성합니다. 이는 특히 함수를 인자로 전달하는 함수에 사용될 때, 표현을 간결하게 하는데 유용합니다.

>(10, 5)
# = true

>(10)
# == x -> 10 > x

filter(x -> x > 10, A)

# 위 표현은 아래와 같이 단축하여 표기할 수 있습니다. 
filter(>(10), A)

map / filter / reduce

배열과 관련해서 가장 많이 사용되는 세 가지 함수는 map(), filter(), reduce() 일 것입니다. map은 특정한 함수나 연산을 배열이나 벡터의 각 요소에 적용하는 함수인데, 이 함수가 하는 일은 Julia의 연산자 브로드캐스팅과 매우 유사하여, 많은 경우 브로드 캐스팅으로 map을 대체할 수 있습니다. 브로드캐스팅은 함수의 뒤나 연산자 앞에 . 을 찍어서 자동으로 적용되도록 할 수 있습니다. 예를 들어 아래 코드는 40 이하의 3으로 나누면 1이 남는 자연수들의 배열인 A의 모든 원소에 대해 각각을 제곱한 B를 구하는 코드입니다.

A = Array(1:3:40)
B = A .^ 2

# 만약 map()을 사용한다면 익명함수나 블럭 표기를 사용
B = map(x -> x ^ 2, A)

B = map(A) do x in
    x ^ 2
    end

브로드캐스팅은 명시적인 루프가 없기 때문에 간단하고 단순한 연산에 대해서는 적용하기가 좋고 성능 측면에서도 더욱 유리합니다. 그러나 논리적으로 모든 맵핑을 브로드캐스팅으로 대체할 수 있다하더라도 map() 을 사용하는 것이 더 나은 경우도 있습니다. 적용하려는 연산이 단순하지 않고 복잡한 로직을 요구하거나 여러 단계의 연산으로 구성된 경우에는 map()을 사용하는 것이 더 좋습니다. 특히 map()은 타입 추론에 더욱 유리하므로 타입 안정성이 중요하다면 map()을 사용하는 것이 더 추천됩니다.

특히 Julia의 map()함수는 사상하려는 함수의 인자의 개수만큼 배열 인자를 받아서 한 번에 적용할 수 있습니다. 예를 들어 두 배열에서 첫번째 배열의 원소는 2를 곱하고, 두 번째 배열의 원소에는 3을 곱한 후 더한 값의 배열을 만들때에는 zip() 함수를 사용합니다. 줄리아에서는 별도의 zip 없이 (물론 zip()이 이러한 용도를 위해 존재하고 이걸 사용해도 됩니다) 다음과 같이 단축하여 적을 수 있습니다.

A = ...
B = ...

C = map((x, y) -> 2x + 5y, zip(A, B))

# 다음과 같이 축약

C = map((x, y) -> 2x + 5y, A, B)

# 여러 배열의 각 원소에 대해 복잡한 코드를 적용하여 연산할 때
W = map(X, Y, Z) do x, y, z in
   ....
end

filter() 함수는 어떤 값이 특정한 조건을 만족하는지를 평가하는 함수를 기준으로, 배열에서 조건을 만족하는 요소만을 골라내는 함수입니다. filter()의 특이한 점으로는 평가함수만 인자로 전달하면 부분 적용된 함수를 리턴할 수 있다는 점입니다. 이 특징을 사용하여 자주 사용하는 필터 함수를 간단하게 만들어서 사용할 수 있습니다.

A = Array(1:3:10)
less_than_10 = filter(<(10))
B = less_than_10(A)
# B = [1, 4, 7]

reduce or fold

reduce()는 여러 개의 값을 하나의 값으로 합칠 때 사용하는 함수입니다. 배열의 합계나 원소의 개수 같은 것을 구할 때 사용합니다.

A = Array(1:10)
reduce((acc, x) -> acc + x, A) |> println
# 55

reduce() 함수 자체에 특이한 부분은 없습니다. 그런데 reduce는 언어에 따라서는 fold라는 이름으로 불리기도 합니다. 배열의 각 원소를 순서대로 접어나가면서 하나의 값으로 만드는 동작을 하기 때문인데요, 주로 함수형 언어에서 fold라는 이름을 많이 씁니다. 그런데, fold는 다시 방향에 따라서 foldl과 foldr로 나뉩니다. 왜 이런 이야기를 하냐면 Julia에는 reduce, foldl, foldr이 모두 존재하기 때문입니다.

표면적인 차이로는 foldl(), foldr()은 각각 연산의 방향에 따라 최적화되어 있다는 점입니다. 단순한 합계나 누적곱을 처리하는 것보다 좀 더 복잡하거나 특별한 자료 구조를 다룰 때에는 어떤 함수를 사용하는지가 중요하게 고민해야 하는 요소가 됩니다. foldl()은 문자열 연결과 같이 앞에서부터 연산해야하는 경우에 유리합니다. 특히 크기가 큰 데이터를 다룰 때 메모리를 효율적으로 사용할 수 있는 것으로 알려져 있습니다. foldr()은 함수의 배열에 대해서 순차적으로 적용하는 등 오른쪽부터 연산해야 하는 경우에 사용되며, 특히 지연된 평가를 통해 원하는 만큼만 계산하기에 좋은 것으로 알려져 있습니다. reduce()는 단순 덧셈이나 곱셈과 같이 결합법칙을 적용할 수 있는, 즉 연산의 순서가 중요하지 않은 작업에 사용될 수 있습니다.

push!, pop!

push!()pop!()은 어떤 집합에 새로운 요소를 추가하거나, 마지막에 추가된 요소를 제거하는 함수입니다. 이름 뒤에 !기호가 붙는 것은 이 함수가 인자로 받는 객체의 내부를 변경한다는 의미입니다. (map()이나 filter() 가 새로운 집합을 만드는 것과 대조적으로 이 함수들은 실제로 기존 집합에 원소를 더하거나 빼는 동작입니다.)

스택의 push, pop 동작처럼 배열의 맨 끝에서 원소를 추가하거나 제거합니다. 그 외에 몇 가지 variation이 존재하니 참고해두는 것이 좋습니다. 참고로 배열 중간에 요소를 삽입할 때에는 pushat!() 이 아니라, insert!()를 사용합니다.

  • push!(A, item)
  • pop!(A)
  • pushfirst!(A, item)
  • popfirst!(A)
  • popat!(A, index)
  • insert!(A, index, item)

first, findfirst, last, findlast

first() 함수는 벡터나 튜플의 첫번째 요소를 구할 수 있는 함수인데, 이름에 함정이 있어서, first(A, n) 과 같이 두 번째 인자에 인덱스를 받아서 앞에서부터 n개의 원소로 된 부분 집합을 얻을 수 있습니다. 함수형 언어에서 보통 take() 에 해당하는 함수라 할 수 있습니다. first()와 반대로 맨 끝의 원소나 뒤에서부터 n개의 원소를 얻고 싶을 때에는 last() 함수를 사용할 수 있습니다.

A = [1,5,2,8,4,6]

first(A) # 1
first(A, 3) # [1,5,2]

last(A) # 6
last(A, 2) # [4,6]

특정한 조건식을 주고 이를 만족하는 첫 값의 위치를 찾는 명령으로는 findfirst()가 있습니다. 줄리아의 벡터에는 파이썬의 index()와 같이 특정한 값이 몇 번째에 위치하는지를 알려주는 함수가 없으므로, findfirst(==(x), A) 와 같은 식으로 사용할 수 있습니다. 비교 연산자를 함수적으로 쓰면서 인자를 하나만 전달하면 부분적용함수가 된다는 사실을 다시 한 번 상기하도록 합시다.

A = [1, 5, 2, 8, 4, 6]
findfirst(iseven, A)
# -> 3

findfirst(==(8), A)
# -> 4

findfirst('o', "hello world")
# -> 5

findfirst("wo", "hello world")
# -> 7:8

연산자를 부분 적용 함수로 만들기

Base.Fix1(), Base.Fix2() 함수는 각각 이항 연산자의 첫번째, 두번째 인자를 고정하여, 주어진 연산자를 함수로 변환하는데 사용될 수 있습니다. 비교 연산자가 아닌 이항 연산자들은 인자를 하나만 전달했을 때 부분 적용 함수가 되지 않으므로 이와 같은 방법을 사용할 수도 있습니다. 대신 클로저를 사용하는 방식이 더 간단하고 직관적으로 느껴질 수도 있을 것 같습니다.

add5 = Base.Fix1(+, 5)
# 다음과 같이 정의한 것과 같음
# add5 = x -> 5 + x

add5(3) == 8
# true

divided_by_2 = Base.Fix2(/, 2)
# divided_by_2 = y -> y / 2

zip

zip 함수는 여러 개의 배열의 각 원소들을 순번대로 묶어서 병렬로 순회하게 해주는 함수입니다. 이미 map() 함수는 첫번째 인자가 여러 개의 인자를 받는 함수인 경우에는 그 인자의 개수만큼 배열을 받아서 zip() 함수가 하는 동작을 포함하고 있습니다만, 여전히 여러 개의 배열의 원소들을 동시에 처리할 때에는 편리하게 사용할 수 있습니다.

zip() 함수는 여러 배열의 각 원소들을 순서대로 짝지어 하나의 튜플로 만들어줍니다. 단 이 튜플을 함수에 전달하려는데, 그 함수가 튜플이 아닌 여러 인자를 받도록 디자인되어 있다면 인터페이스가 호환되지 않습니다. 이 경우에는 두 가지 해결책이 있습니다.

  1. f(xs...) 과 같이 튜플 뒤에 ... 을 써서 튜플의 내용을 언패킹하여 인자의 목록으로 변환하여 넘겨주는 방법이 있습니다.
  2. 다른 방법으로는 splat() 함수가 있습니다. 이 함수는 여러 인자를 받는 함수를 튜플 하나를 받는 함수로 변환해주는 함수입니다.

xs... 과 같이 사용되는 ... 을 스플랫 연산자(splat operator)라고 하는데, 튜플 뿐만 아니라 다른 집합에 대해서도 적용이 가능합니다. 예를 들어 두 개의 1차원 백터는 다음과 같이 하나로 합칠 수 있습니다.

A = [1, 2, 3]
B = [7, 8, 9]

[A..., B...]

# == vcat(A, B)

(단, 경험상 이렇게 배열을 합치는 경우에는 스플랫 연산자보다는 vcat을 사용하는 편이 더 좋은 성능을 보였던 것 같습니다.)

Read more

워드프레스에서 고스트로 이전

워드프레스에서 고스트로 이전

이 글을 쓰면서도 믿기 힘든 사실인데, 블로그라는 걸 처음 시작한지가 20년이 되었습니다. 이글루스에서 처음 시작했다가, SK컴즈가 인수한다고 발표함과 동시에 워드프레스로 플랫폼을 옮겼죠. 워드프레스오 옮긴 이후에는 호스팅 환경을 이리 저리 옮기긴 했지만 거의 18년 가까이 워드프레스를 사용해온 것 같습니다. 그 동안 워드프레스는 블로깅 툴에서 명실상부한 범용CMS로 발전했습니다. 사실 웬만한 홈페이지들은 이제

By sooop
띄어쓰기에 대한 생각

띄어쓰기에 대한 생각

업무 메일을 쓸 때 가장 많이 쓰는 말 중에 하나가 메일 말미에 ‘업무에 참고 부탁 드립니다.‘인데요, 어느 날부터 아웃룩에서 이 ‘부탁 드립니다’가 틀렸다고 맞춤법 지적을 하기 시작했습니다. 맞는 말은 ‘부탁드립니다’라고 붙여 쓰는 거라고. 사실 아래아한글 시절부터 이전의 MS워드까지, 워드프로세서들의 한국어 맞춤법 검사 실력은 거의 있으나 마나 한

By sooop

구글 포토에서 아이클라우드로 탈출한 후기

한 때 구글 포토가 백업 용량을 무제한으로 제공해 주겠다고해서, 구글 포토를 사용해서 사진을 백업해왔습니다. 물론 이 이야기의 결말은 저나 이 글을 읽고 있는 여러분이나 모두 알고 있습니다. 사실 AI에게 학습 시킬 이미지 데이터를 모으기 위한 것일 뿐이라거나 하는 이야기는 그 당시에도 있었습니다만, 에이 그래도 구글인데 용량은 넉넉하게 주겠지…하는 순진한

By sooop
git 사용자 선택 팝업 생략하기

git 사용자 선택 팝업 생략하기

명령줄에서 git을 사용해서 github에 있는 저장소에 접근하려고하면 아래와 같은 UI가 표시되면서 사용자를 선택하라고 합니다. 이게 처음에는 사용자 인증 정보가 저장이 안돼서 생기는 문제라고 생각했었는데, 이미 저장되어 있는 github 사용자가 2개가 되면서, 어떤 사용자로 접근할 것인지를 묻는 내용이었네요. 매번 이렇게 계정을 선택하라고 묻는 이유는 git 이라는 계정이 자동으로 등록되었기 때문인데요, 이

By sooop