clojure 1.11.0-rc1 변경점
Table of Contents
Clojure 1.11.0-rc1 이 나왔다. https://github.com/clojure/clojure/blob/master/changes.md
간략하게 뭐가 바꼈나 보자.
1 키워드 인자 기능 향상
Clojure는 함수를 키워드를 받는 함수를 디자인할 때 두 가지를 선택해야한다.
1.1 key-value 쌍을 인자로 나열하기.
(defn configure [val & {:keys [debug verbose] :or {debug false, verbose false}}] (println "val =" val " debug =" debug " verbose =" verbose)) (configure 10) ;;val = 10 debug = false verbose = false (configure 5 :debug true) ;;val = 5 debug = true verbose = false ;; Note that any order is ok for the kwargs (configure 12 :verbose true :debug true) ;;val = 12 debug = true verbose = true
이렇게 key-value 쌍을 넣으면 저절로 바인딩 된다. 하지만 key-value 라고 아래처럼 사용할 수는 없다.
(configure 12 {:debug true}) ;; IllegalArgumentException ;; No value supplied for key: {:debug true}
1.2 key-value 쌍을 hashmap 하나만 받아서 사용하기.
(defn configure [val {:keys [debug verbose] :or {debug false, verbose false}}] (println "val =" val " debug =" debug " verbose =" verbose)) (configure 12 {:debug true}) ;;val = 12 debug = true verbose = false
하지만 이것은 아래처럼 쓸 수 없다.
(configure 12 :debug true) ;; Wrong number of args (3) passed to:
1.3 위 두개를 합칠 순 없을까?
Clojure 1.11.0 부터는 가능하다. 의존성을 추가하자.
{:path ["src"] :deps {org.clojure/clojure {:mvn/version "1.11.0-rc1"}}}
(defn configure [val & {:keys [debug verbose] :or {debug false, verbose false}}] (println "val =" val " debug =" debug " verbose =" verbose)) (configure 10) (configure 10 :debug true) (configure 10 {:debug true})
이렇게 가능해졌다. 이제 둘 중에 하나를 선택할 필요가 없어졌다. 둘다 지원하자.
참고자료
2 update-keys, update-vals
clojure 자료구조를 다루다보면, 해시맵의 key만 바꾸거나, value만 순회하면서 바꾸는 일이 비일비재하다.
update-keys
, update-vals
는 이런 경우를 위해 추가된 함수들이다.
구현을 한번 보자.
(defn update-vals "m f => {k (f v) ...} Given a map m and a function f of 1-argument, returns a new map where the keys of m are mapped to result of applying f to the corresponding values of m." {:added "1.11"} [m f] (with-meta (persistent! (reduce-kv (fn [acc k v] (assoc! acc k (f v))) (if (instance? clojure.lang.IEditableCollection m) (transient m) (transient {})) m)) (meta))) (defn update-keys "m f => {(f k) v ...} Given a map m and a function f of 1-argument, returns a new map whose keys are the result of applying f to the keys of m, mapped to the corresponding values of m. f must return a unique key for each key of m, else the behavior is undefined." {:added "1.11"} [m f] (let [ret (persistent! (reduce-kv (fn [acc k v] (assoc! acc (f k) v)) (transient {}) m))] (with-meta ret (meta m))))
3 uuid 만들기
원래 clojure에서 UUID를 만들 때는 곧바로 자바의 java.util.UUID
생성자로 생성했다.
(java.util.UUID/randomUUID) => #uuid "b8603191-cd93-47d5-abd6-8bf2c0fbb036"
그런데 사실 clojure에는 uuid?
라는 함수는 있지만 uuid는 자바로 만들어야 했다.
1.11.0 이 되면서 random-uuid
함수가 코어로 들어왔다.
(random-uuid) => #uuid "65fb9636-814c-4a9f-bf9e-22c1abd7d039"
4 기본 값 파싱 함수들
현재까지 이 함수가 없었던 것에 놀랍다.
이번에 추가된 함수는 parse-long, parse-double, parse-boolean, parse-uuid
이다.
왜 parse-int
는 없지?
라고 생각 할 수 있다. 하지만 clojure는 기본적으로 숫자값을 Long을 사용한다.
(type 123) => java.lang.Long
위 코드가 없을 때는 java에 있는 메소드를 이용했는데 걸리적 거리는 점은 아래와 같다.
- integer parsing: Integer/parseInt (992), Integer/valueOf (98), Integer. (270),Long/parseLong (492), Long/valueOf (66), Long. (75)
- double parsing: Double/parseDouble (186), Double/valueOf (25), Double. (23), Float/parseFloat (52), Float/valueOf (3)
- uuid parsing: UUID/fromString (134)
- boolean parsing: Boolean/parseBoolean (67)
위 내용은 깃허브에서 Clojure 코드가 JDK 함수를 파싱할 때 사용한 메소드 카운트이다. 확실히 뭔가 파편화됨을 알 수 있다.
또한 한가지 걸리는 점은 예외이다. 클로저에서는 값이 유효하지 않은 경우 예외를 던지는 것보다는 nil
을 리턴하는게 일반적이다.
하지만 자바 메소드는 대부분 NumberFormatException
예외를 던진다.
이번에 추가된 코드는 NumberFormatException
을 캐치해서 nil
을 리턴한다.
아래는 이번에 추가된 parse-long
소스코드다.
(defn parse-long (if (string? s) (try (Long/valueOf s) (catch NumberFormatException _ nil)) (throw (IllegalArgumentException. (parsing-err s))))))
참고자료
5 core에 새로운 숫자 관련 함수 추가
abs
가 추가되었다. clojure타입에 최적화되어 있다고 한다. (long, double, ratio, bigint, bigdecimal)
(abs -1) ; 1 (abs -1/2) ; 1/2 (abs -1.1) ; 1.1 (abs -1.0M) ; 1.0M (abs (bigint "-1000000000000000000000000000000000000000000000000000000000000000000000000000")) ; => 1000000000000000000000000000000000000000000000000000000000000000000000000000N
NaN
, infinite?
같은 경우는 자바 메서드 래퍼다
(NaN? (/ (* 10 0.0) 0.0)) (infinite? (/ 10 0.0))
6 clojure.math
java의 math 패키지 wrapper clojure.math 가 런칭되었다.
Figure 1: clojure.math
7 interation
clojure는 iteration이라는 새로운 추상화를 제시한다. iteration에 대해서는 나중에 제대로 다뤄보자. 아래는 리치히키의 코드이다.
(defn iteration "creates a seqable/reducible given step!, a function of some (opaque continuation data) k step! - fn of k/nil to (opaque) 'ret' :some? - fn of ret -> truthy, indicating there is a value will not call vf/kf nor continue when false :vf - fn of ret -> v, the values produced by the iteration :kf - fn of ret -> next-k or nil (will not continue) :initk - the first value passed to step! vf, kf default to identity, some? defaults to some?, initk defaults to nil it is presumed that step! with non-initk is unreproducible/non-idempotent if step! with initk is unreproducible it is on the consumer to not consume twice" [step! & {:keys [vf kf some? initk] :or {vf identity kf identity some? some? initk nil}}] (reify clojure.lang.Seqable (seq [_] ((fn next [ret] (when (some? ret) (cons (vf ret) (when-let [k (kf ret)] (lazy-seq (next (step! k))))))) (step! initk))) clojure.lang.IReduceInit (reduce [_ rf init] (loop [acc init ret (step! initk)] (if (some? ret) (let [acc (rf acc (vf ret))] (if (reduced? acc) @acc (if-let [k (kf ret)] (recur acc (step! k)) acc))) acc)))))
참고자료
8 as-alias
이것은 clojure의 spec을 만들 때 요긴하게 쓸 수 있다.
우리는 spec을 만들 때 qualified keyword 를 쓴다. 즉, 키워드이름에 네임스페이스까지 넣어서 사용하는 것이다. 그런데 때로 현재 네임스페이스가 아니라 좀 더 namespace에 이 도메인의 의미를 넣어주고 싶을 때가 있을 수 있다. 혹은 파일은 있지만 나는 해당 함수들을 로드하고 싶은 것이 아니라, 해당 키워드만 쓰고 싶은 것이다.
이경우 as-alias
를 쓰면 간단하게 된다.
이것이 없는 경우 create-ns
으로 런타임에 로딩없이 네임스페이스를 만들 수 있다. 그 다음에 alias를 하는 것이다.
아래 코드를 보자.
(alias 'dm (create-ns 'my.real.domain)) (def user ::dm/user) user ;; => :my.real.domain/user
현재 나의 프로젝트에는 my.real.domain
라는 네임스페이스는 없다.
이번에 추가된 :as-alias
를 추가하자
(ns demo (:require [my.real.domain :as-alias d])) (def user2 ::d/user) user2 ;; => :my.real.domain/user
참고자료