Julia는 가능한 한 많은 코드를 JIT 컴파일할 수 있는 함수 안에 넣도록 권장해요. 그리고 작은 함수를 많이 만드는 것이 설계상 성능이 좋아요.
그러면 작고 단순한 함수가 많이 남게 되고, 이런 함수들을 조합해야 복잡한 작업을 처리할 수 있어요.
한 가지 확실한 방법은 함수 호출을 중첩하는 거예요. 다음 예제는 억지로 만든 감이 있지만, 요점을 잘 보여줘요.
julia> first.(titlecase.(reverse.(["my", "test", "strings"])))
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
이 방법의 단점은 중첩이 깊어질수록 가독성이 빠르게 떨어진다는 거예요.
더 단순하고 유연한 방법이 필요해요.
수학자들이 아주 좋아하는 기법이고, Julia는 수학의 문법을 그대로 가져왔어요.
원하는 만큼의 함수를 ∘ 연산자로 composed할 수 있어요 (\circ를 입력한 뒤 탭을 눌러요). 그 결과는 하나의 함수처럼 사용할 수 있어요.
julia> compfunc = first ∘ titlecase ∘ reverse
first ∘ titlecase ∘ reverse
julia> compfunc.(["my", "test", "strings"])
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
# alternative syntax, giving the same result
julia> (first ∘ titlecase ∘ reverse).(["my", "test", "strings"])
몇 가지 짚어 볼 점이 있어요:
또 다른 방법은 _수학자_보다는 _프로그래머_의 접근 방식이라고 할 수 있어요.
Pipelines는 오래전부터 Unix 셸 스크립트에서 사용되어 왔고, 최근에는 주류 프로그래밍 언어에서도 인기를 얻었어요 (F#이 그 도입을 개척했다는 평가를 받기도 해요).
기본 개념은 어떤 데이터에서 시작해서 일련의 함수들을 거쳐 결과를 얻는 거예요.
파이프 연산자는 |>예요 (F#과 최근 버전의 R에서처럼요). Julia에는 브로드캐스트 버전인 .|>도 있어요.
julia> ["my", "test", "strings"] .|> reverse .|> titlecase .|> first
3-element Vector{Char}:
'Y': ASCII/Unicode U+0059 (category Lu: Letter, uppercase)
'T': ASCII/Unicode U+0054 (category Lu: Letter, uppercase)
'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
이제 실행은 엄격하게 왼쪽에서 오른쪽으로 진행되고, 각 함수의 출력이 화살표 방향으로 흘러 다음 함수의 입력이 돼요.
합성과 파이프라이닝을 설명할 때 사용한 함수들이 모두 단일 인자를 받는다는 건 우연이 아니에요.
몇몇 순수 함수형 언어는 첫 번째 인자를 함수로 파이프하되, 다른 인자들도 포함할 수 있게 해요.
반대로 Julia는 파이프라인에서 함수 이름(또는 그에 상응하는 것)만 기대하고, 추가 인자는 받지 않아요.
여기에는 중요한 기술적 이유가 있어요 (currying이 언어 설계의 표준적인 부분이 아니라는 점과 관련이 있어요). 커링을 전혀 이해하지 못하는 많은 사람은 이 한계가 부주의한 실수가 아니며, 향후 Julia 버전에서 바뀔 가능성도 낮다는 점을 그냥 받아들이면 돼요.
필요한 일을 해 주는 단일 인자 함수가 필요해요. 다행히 Julia에서는 새 함수를 정의하기가 쉬워요.
가장 간단하게는 anonymous function을 사용할 수 있어요. 예를 들어, 입력 문자열 하나가 있고 밑줄을 기준으로 나누고 싶다면:
julia> "my_test_strings" |> (s -> split(s, '_'))
3-element Vector{SubString{String}}:
"my"
"test"
"strings"
그 배열은 이전처럼 다른 함수로 파이프할 수 있어요.
이 경우 익명 함수를 괄호로 감싸는 건 선택 사항이지만, 일반적으로 모호함을 줄이는 데 유용한 방법이에요.
마찬가지로, 프로그램 앞부분에서 이름 있는 함수를 만들어 필요할 때마다 재사용할 수도 있어요.
이 연습 문제에서는 고등학교 커플이 소셜 미디어에서 사랑을 고백할 수 있도록, 두 사람의 이니셜이 들어간 유니코드 하트를 만들어 줄 거예요:
❤ J. + M. ❤
cleanupname 메서드를 구현해요.
이름을 받아서 - 문자를 모두 없애고 공백으로 바꿔요.
이름의 앞뒤에 있는 공백도 제거해요.
julia> cleanupname("Jane-Ann")
"Jane Ann"
firstletter 메서드를 구현해요.
이름을 받아서 첫 글자를 문자열로 반환해요.
이전 단계에서 정의한 cleanupname을 다시 사용하고, 다른 함수들과 조합해 봐요.
julia> firstletter("Jane")
"J"
initial 메서드를 구현해요.
이름을 받아서 첫 글자를 대문자로 만들고 뒤에 마침표를 붙여 반환해요.
이전 단계에서 정의한 firstletter를 다시 사용해 봐요.
initial("Robert")
"R."
couple 메서드를 구현해요.
이름 두 개를 받아서 양쪽에 하트 이모지를 붙인 이니셜을 반환해요.
이전 단계에서 정의한 initial을 다시 사용해 봐요.
couple("Blake Miller", "Riley Lewis")
"❤ B. + R. ❤"
Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.