#java8 sudo add-apt-repository ppa:webupd8team/java sudo apt-get update sudo apt-get install oracle-java8-installer #leiningen wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein chmod a+x lein sudo mv lein /usr/local/bin
Build 및 의존성 관리 도구. npm과 같은 역할. (Windows의 경우 자동 Installer도 제공.)
# Project 생성 lein new app project-name # Project 실행 ## 해당 Project Directory 진입 후에 lein run # 프로젝트 배포 (실행가능한 jar 파일 만들기) ## 해당 Project Directory 진입 후에 lein uberjar java -jar ..\target\uberjar\~~-standalone.jar
코드 테스트 도구
lein repl
MVC Framework
#Leiningen으로 쉽게 프로젝트 구성! lein new luminus my-app cd my-app lein run
$(document).ready(function() { $('select').material_select(); }); (.ready (jquery (js* "document")) #(.material_select (jquery (js* "'select'")))))
https://cursive-ide.com/ http://manmyung.github.io/2015/03/17/post.html http://archive.is/CseCi
https://clojure.org/reference/data_structures
예약어와 비슷한 개념. Java나 Clojure에서 제공하는 패키지/클래스/메서드/함수명들.
:(콜론)으로 표기. Keyword를 평가하면 그 자신을 반환하는 특징때문에 struct(구조체)나 Map의 Key로 사용하기에 좋다.
(def person {:name "Luke" :age 33}) person => {:name "Luke", :age 33}
;생성. 이 때, 어떤 요소든 추가 가능. '(1 "a" :key +) => (1 "a" :key +) (list 1 2 3 4) => (1 2 3 4) ;생성 및 원소 추가 (cons 1 '()) => (1) (first '(1 "a" :key +)) => 1 ;첫 번째 요소 제외 나머지 요소 평가 (rest '(1 "a" :key +)) => ("a" :key +) (last '(1 "a" :key +)) => + (nth '(1 2 3 4) 3) => 4 (conj '(1 2 3 4) 999) => (999 1 2 3 4)
(conj '(1 2 3) 4) ;; (4 1 2 3) (pop '(1 2 3 4)) ;; (2 3 4) (peek '(1 2 3 4)) ;; 1
; 생성 [1 "a" :key] => [1 "a" :key] (cons 1 []) => (1) (vector 1.0 1 "hello") => [1.0 1 "hello"] (get [3 2 1] 0) => 3 (get ["hello" {:key "value"} 1.0] 1) => {:key "value"} ;원소 추가 (conj [1 2 3] 4) => [1 2 3 4]
(conj [1 2 3] 4) ;; [1 2 3 4] (pop [1 2 3 4]) ;; [1 2 3] (peek [1 2 3 4]) ;; 4
{:name "Luke" :age 30} (hash-map :name "Luke" :age 30) (get {:a 0 :b {:c "test"}} :b) => {:c "test"} (get-in {:a 0 :b {:c "test"}} [:b :c]) => "test" ; key가 없는 경우 default 반환. 만약 default 없으면 nil. ; 아래 식은 모두 같다. (get {:a 0 :b 1} :c "This is default!") ({:a 0 :b 1} :c "This is default!") (:c {:a 0 :b 1} "This is default!") => "This is default!" ;key만 반환 (keys {:key1 111 :key2 "hello"}) => (:key1 :key2) ;값만 반환 (vals {:key1 111 :key2 "hello"}) => (111 "hello") ;추가/수정 (assoc {:key1 111 :key2 "hello"} :key3 12.3) => {:key1 111, :key2 "hello", :key3 12.3} (assoc {:key1 111 :key2 "hello"} :key2 "world") => {:key1 111, :key2 "world"} ;삭제 (dissoc {:key1 111 :key2 "hello"} :key2) => {:key1 111} ;병합 (merge {:key1 111 :key2 "hello"} {:key1 777 :keyX 123456}) => {:key1 777, :key2 "hello", :keyX 123456}
;생성 #{1 2 3} (hash-set 1 1 2 2) => #{1 2} (set [1 1 2 2]) => #{1 2} ;추가 (conj #{:a :b} :b) => #{:b :a} ;제거 (disj #{:a :b} :b) => #{:a} ;원소 포함 여부 (contains? #{:a :b} 1) => false ;원소 가져오기 ;아래 식은 모두 같다 (get #{:a :b} :a) (#{:a :b} :a) (:a #{:a :b}) => :a
(not (= :hello :hello)) ;축약 표현 (not= :hello :hello) => false
;조건 만족 (and "hello" 111 :aaa) => :aaa ;조건 불만족 (and "hello" nil 123) => nil
;조건 만족 (or false "hello" 111) => "hello" ;조건 불만족 (or false nil) => nil (or false) => false
;;Collections가 비어있는 지 확인 (empty? []) => true ;(not (empty? x))보다 관용적. (seq []) => nil ;;모든 요소에 대한 조건 검사 ;every : 모든 요소에 대해 검사 결과가 True? (every? even? [2 4 6]) => true (every? (fn [x] (= x :apple)) [:apple :banana]) (every? #(= % :apple) [:apple :banana]) => false ;not-any : 모든 요소에 대해 검사 결과가 False? (not-any? #(= % :apple) [:banana :kiwi]) => true ;some : 요소 중 일부라도 True? ;진위 함수가 평가한 값이 반환되며, 이 때 진위 함수 값이 여러개이면 첫 번째로 True인 값이 반환된다. ;진리 함수가 논리적으로 false이면 nil(논리적 false). (some #{0} [1 2 3 4 5]) => nil (some #{3 5} [1 2 3 4 5]) => 3 (some #{nil} [1 2 3 4 5]) => nil (some #{false} [1 2 3 4 5]) => nil
(if true "TRUE!" "FALSE!") =>"TRUE!" (if false "TRUE!") => nil ;false에 해당하는 값이 존재하지 않으므로
(let [result (> 1 3)] (if result "TRUE" "FALSE")) (if-let [result (> 1 3)] "TRUE" "FALSE")
if문에서 여러 Form을 괄호로 묶어 실행할 때 사용.
(if true (do (println "TRUE!") "TRUE return value!") (do (println "FALSE!") "FALSE reutrn value!")) ;결과 TRUE! => "TRUE return value!"
True이면 본문(함수 내용)을 평가하고, False이면 nil 반환
(when true (println "TRUE!") "TRUE return value") ;결과 TRUE! => "TRUE return value" (defn test-when [arg] (when arg "return value")) (test-when 0) (#(when % "return value") 0) => "return value" (test-when false) => nil
(let [result (> 3 1)] (when result "TRUE")) => "TRUE" (when-let [result (> 3 1)] "TRUE") => "TRUE"
(let [fruit "apple"] (cond (= fruit "banana") "yellow" (= fruit "kiwi") "brown" (= fruit "melon") "green" :else "nothing")) => "nothing" ;아래 조건식이 모두 만족하지만 첫 번째 조건 평가하고 종료. (let [x 10] (cond (> x 1) "first" (> x 2) "second" (> x 3) "third")) => "first" ;default절의 원리. 만약, 기술안하면 nil. (let [x 10] (cond (> x 111) "first" (> x 222) "second" (> x 333) "third" 0 "DEFAULT!"))
(defn test-condp [num] (condp > num 10 "A" 20 "B" 30 "C" "X")) (test-condp 11) ;=> "B"
(let [fruit "apple"] (case fruit "banana" "yellow" "kiwi" "brown" "melon" "green" "nothing")) => "nothing"
def(전역) vs. let(지역)
(let [sym1 val1 sym2 val2 sym3 val3 ... ] <body>) ;symN은 valN이 Binding 된다. ;<body>은 let의 결과인 symN을 재이용할 수 있다. (Lexical Scope) ;마지막에 기술된 <body>는 Return value.
;let에서 계산된 결과 재활용 (defn square-corners [bottom left size] (let [top (+ bottom size) right (+ left size)] [[bottom left] [top left] [top right] [bottom right]])) (square-corners 111 222 333) => [[111 222] [444 222] [444 555] [111 555]]
(def my-vector ["A" "B" "C" "D"]) ; 1. sym1에 my-vector의 첫 번째 원소와 binding ; 2. elements에 my-vector의 나머지 원소들과 binding (let [[sym1 & elements] my-vector] [sym1 elements]) => ["A" ("B" "C" "D")]
;단순 재귀 ;;StackOverFlow (defn factorial1 [n] (if (= n 1) n (*' n (factorial (dec n))))) (factorial1 99999) ;loop & recur (defn factorial2 [n] (loop [x n, result 1] (if (= x 1) result (recur (dec x) (*' result x))))) (factorial2 10000)
(defn fibonacci [n] (loop [x 0, result [1 1]] (if (= x n) result (recur (inc x) (conj result (+' (get result x) (get result (inc x)))))))) (fibonacci 20) => [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711]
(defn multiplicationTable [n] (loop [x 1] (println n "x" x "=" (* n x)) (if (= x 9) :end! (recur (inc x))))) (multiplicationTable 3) ;결과 3 x 1 = 3 3 x 2 = 6 3 x 3 = 9 3 x 4 = 12 3 x 5 = 15 3 x 6 = 18 3 x 7 = 21 3 x 8 = 24 3 x 9 = 27 => :end!
(loop [iteration 0] (println (str "Iteration : " iteration)) (if (> iteration 3) (println "End!") (recur (inc iteration)))) (defn recursive-printer ([] (recursive-printer 0)) ([iteration] (println (str "Iteration : " iteration)) (if (> iteration 3) (println "End!") (recursive-printer (inc iteration))))) (recursive-printer) ;위 둘다 같은 결과 Iteration : 0 Iteration : 1 Iteration : 2 Iteration : 3 Iteration : 4 End! => nil
Sequence 순회. (foreach)
(for [i (range 5)] i) => (0 1 2 3 4) ;name : symbol이나 키워드의 이름을 가져온다. (for [alphabet [:a :b :c] number [:1 :2]] (str (name number) (name alphabet))) => ("1a" "2a" "1b" "2b" "1c" "2c") ;:let을 이용한 binding (for [alphabet [:a :b :c] number [:1 :2] :let [n (name number) a (name alphabet) all (str n a)]] all) => ("1a" "2a" "1b" "2b" "1c" "2c") ;:when 조건을 만족할 때만 반복 (for [alphabet [:a :b :c] number [:1 :2] :let [n (name number) a (name alphabet) all (str n a)] :when (= number :1)] all) => ("1a" "1b" "1c")
var에 대한 접근을 조직하고 제어하는 방법. java의 package와 같은 개념.
;생성/전환 (ns luke.favfoods) ;현재 namespace 조회 *ns* ;호출 (def fav-food "noodle") fav-food first-clojure.core/fav-food => "noodle"
; 가장 마지막에 계산한 값을 반환한다! (defn func-name "The docstring(comment) is optional." [arg1 arg2 arg3] (println arg1 arg2 arg3) "This text is garbage." "The return value is optional.") (func-name "hello" "world" "!!!") ;결과 hello world ! => "The return value is optional."
OOP의 Method Overloading과 같은 개념
(defn func-name "The docstring(comment) is optional." ([] (println "Hello Korea!")) ([arg1 arg2 arg3] (println arg1 arg2 arg3))) (func-name) ;결과 Hello Korea! => nil
가변 인자
(defn varargs [& args] (str args)) (varargs "hello" "world" "!") => "(\"hello\" \"world\" \"!\")"
Function의 arguments에서 Collections를 추려 뽑기. Collections 일부만 Binding 하기.
(let [[color size] ["blue" "small"]] (str color " " size)) => "blue small" ;별칭을 지정하여 전체 출력 (let [[color size :as all] ["blue" "small"]] (str color " " size " | " all)) => "blue small | [\"blue\" \"small\"]" (defn destructuring-vector1 [[arg1 arg2]] arg2) (destructuring-vector1 [1 2 3 4 5]) ;결과 => 2 (defn destructuring-vector2 [[arg1 arg2 & args]] (println (str "arg1 : " arg1)) (println (str "arg2 : " arg2)) (println (str "args : " (clojure.string/join args)))) (destructuring-vector2 ["Hello " "World" "Korea" "China" "Japan"]) ;결과 arg1 : Hello arg2 : World args : KoreaChinaJapan => nil ;키워드 ":as"를 쓰면 Collections 전체 Binding (defn destructuring-vector3 [[val1 val2 :as nickName]] (str val1 " " val2 " " (count nickName))) (destructuring-vector3 [1 2 3 4]) => "1 2 4"
(defn destructuring-map1 [{val1 :key1 val2 :key2}] (println (str "val1 : " val1)) (println (str "val2 : " val2))) (destructuring-map1 {:key1 28.22 :key2 81.33}) ; 결과 val1 : 28.22 val2 : 81.33 => nil (defn destructuring-map2 [arg] (println (:key2 arg))) (destructuring-map2 {:key1 "Hello" :key2 "World"}) ;결과 World => nil
;:or를 쓰면 해당 요소가 없을 때를 위한 default 값 설정 가능. (let [{fruit1 :fruit1, fruit2 :fruit2 :or {fruit2 "banana"}} {:fruit1 "apple"}] (str fruit1 " " fruit2)) => "apple banana" ;:as를 쓰면 Collections 전체 binding 가능. (let [{fruit :fruit :as all} {:fruit "apple"}] (str fruit " | " all)) => "apple | {:fruit \"apple\"}" ;;:keys를 이용한 간략화(추상화). ;;;보통 심볼 이름과 key 이름을 갖게 하므로 아래와 같은 식 사용 가능. ;;;Destructuring의 가장 흔한 방식 ;변경전 (let [{fruit1 :fruit1, fruit2 :fruit2} {:fruit1 "apple", :fruit2 "banana"}] (str fruit1 " " fruit2)) ;변경후 (let [{:keys [fruit1 fruit2]} {:fruit1 "apple", :fruit2 "banana"}] (str fruit1 " " fruit2)) => "apple banana"
(let [[x y] [1 2 3]] [x y]) => [1 2] ;Underscore(_)의 의미는 "이 Binding은 Skip!" (let [[_ _ z] [1 2 3]] z) => 3 ;반환값이 Underscore인 경우 가장 마지막 Underscore에 대응되는 값 반환. ;관용적이지 않다! (let [[_ _ z] [1 2 3]] _) => 2 ;함수에서 :keys를 이용한 간략화 (defn fruit-kind [{:keys [fruit1 fruit2]}] (str fruit1 " " fruit2)) (fruit-kind {:fruit1 "apple", :fruit2 "banana"})
고차 함수로 이용.
((fn [a] (* a 3)) 8) (#(* % 3) 8) => 24 (map (fn [name] (str "Hi, " name)) ["Luke", "James"]) (map #(str "Hi, " %) ["Luke", "James"]) => ("Hi, Luke" "Hi, James") ;매개 변수가 여러개인 경우 (#(* %1 %2 %3) 2 3 4) => 24
참고로 익명 함수에는 4개의 Java Interface가 Implement 된다.
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFunction.java https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/IFn.java
(defn adder-builder [arg] #(+ % arg)) ;함수를 인자로 넣는다. ;이 때 adder는 함수를 반환값으로 받으므로 함수가 아닌 값! 그래서 def (def adder (adder-builder 1)) (adder 10) ;결과 => 11
(defn grow [name direction] (if (= direction :small) (str name " is growing smaller") (str name " is growing bigger"))) ((partial grow "hyuk") :small) => "hyuk is growing smaller"
여러 함수들을 합성하여 하나의 새로운 함수 생성.
(defn toggle-grow [direction] (if (= direction :small) :small :big)) (defn show-direction [direction] (str "Direction : " direction)) (defn surprise [direction] ((comp show-direction toggle-grow) direction)) (surprise :small) => "Direction : :small"
;;가장 안쪽에 있는 함수들이 무한 처리 작업 (take 5 (range)) => (0 1 2 3 4) (take 5 (repeat "x")) => ("x" "x" "x" "x" "x") ;repeat는 중간처리 결과를 한번만 실행하고 반복 ;repeatedly는 인수로 받은 함수를 반복 실행 (take 5 (repeatedly #(rand-int 10))) => (0 8 8 0 0) ;cycle은 Collections를 인수로 받는다 (take 5 (rest (cycle ["a" "b" "c"]))) => ("b" "c" "a" "b" "c")
결과값은 입력 Collections의 각 요소에 특정 기능의 함수가 적용된 새로운 Collections.
(def data [:a :b :c :d :e]) (map #(str %) data) => (":a" ":b" ":c" ":d" ":e") ;각각의 요소에 대해 문자열로 변경된 Collections. (defn get-text [alphabet number] (str alphabet number)) (def data1 ["a" "b" "c" "d" "e"]) (def data2 [1 2 3]) (map get-text data1 data2) => ("a1" "b2" "c3")
;;factorial ;range 1 6 : 1~5 (reduce #(* %1 %2) (range 1 6)) (reduce #(* %1 %2) (map #(inc %) (take 5 (range)))) => 120
Type | Communication | Coordination(≒ Thread-safe) |
---|---|---|
atom | Synchronous | Uncoordnated |
ref | Synchronous | Coordnated |
agent | Asynchronous | Uncoordnated |
독립적인 동기적 변경
(def who-atom (atom :before-change)) ;값으로 변경 (reset! who-atom :after-change) @who-atom => :after-change ;함수로 변경 (swap! who-atom #(case % :before-change "before!" :else)) @who-atom => :else
Transaction이 필요한 동기적 변경 (Critical Section에 접근하는 경우)
(def acc1 (ref 100)) (def acc2 (ref 200)) (println @acc1 @acc2) ;100 200 (defn transfer-money [a1 a2 amount] (dosync (alter a1 - amount) (alter a2 + amount) amount)) (let [n 2] (future (dotimes [_ n] (transfer-money acc1 acc2 10))) (future (dotimes [_ n] (transfer-money acc1 acc2 10))) (future (dotimes [_ n] (transfer-money acc1 acc2 10)))) (println @acc1 @acc2) ;40 260
독립적인 비동기적 변경. 아래 변경하는 함수들은 argument로 함수를 받는다.
(def who-agent (agent :before-change)) (send who-agent #(case % :before-change "before!" :else)) @who-agent => "before!"
(def who-agent (agent :before-change)) (send who-agent #(throw (Exception. "Exception!"))) ;의도적인 Exception (send who-agent #(case % :before-change "before!" :else)) ;CompilerException java.lang.RuntimeException: Agent is failed, needs restart, compiling:(...) @who-agent ;값이 변경되지 않음. ;=> :before-change ;;이 경우 restart-agent를 사용해야 한다. ;;restart-agent 함수로 재시작하기전까지 실패한 상태 유지. ;;;restart-agent는 Error를 제거하고 agent의 초기값을 재설정한다. (restart-agent who-agent :before-change) (send who-agent #(case % :before-change "before!" :else)) ;성공! @who-agent ;=> "before!"
Java 라이브러리 가져다 쓰기
https://clojure.org/reference/java_interop
한개의 함수를 대상으로 다형성 구현. Java에서 Abstract Method를 정의하고, 상속된 Class에서 이를 구현하는 것과 비슷함.
;① 분기(dispatch)를 결정하는 함수. 마지막 argument가 분기하는 기준. (defmulti polymorphism1 class) ;② 구현체 (defmethod polymorphism1 String [input] (type input)) (defmethod polymorphism1 Keyword [input] (type input)) (defmethod polymorphism1 Long [input] (type input)) (defmethod polymorphism1 :default [input] "NONE!") ;;실행 (polymorphism1 213) ;=> java.lang.Long (polymorphism1 :hello) ;=> clojure.lang.Keyword
;① 분기(dispatch)를 결정하는 함수. 마지막 argument가 분기하는 기준. ;;이 때 인자는 Map (defmulti greeting #(% :language)) ;② 구현체 (defmethod greeting "english" [parmas] "Hello!") (defmethod greeting "french" [parmas] "Bonjour!") (defmethod greeting :default [params] (throw (IllegalArgumentException. (str "Unknown language : " (params :language))))) ;실행 (greeting {:id 1, :language "english"}) ;=> "Hello!" (greeting {:id 2, :language "french"}) ;=> "Bonjour!" (greeting {:id 3, :language "korean"}) ;CompilerException java.lang.IllegalArgumentException: Unknown language : korean, compiling:(...)
;① 분기(dispatch)를 결정하는 함수 (≒ Interface) ;; 함수로도 argument를 받을 수 있다. (defmulti polymorphism2 #(if (< % 3) :fn1 :fn2)) ;② 구현체 (defmethod polymorphism2 :fn1 [_] "Function 1") (defmethod polymorphism2 :fn2 [_] "Function 2") ;실행 (polymorphism2 5) ;=> "Function 2"
다수의 함수를 대상으로 다형성 구현.
(defprotocol abstraction (impl [this])) (extend-protocol abstraction String (impl [this] (type this)) Keyword (impl [this] (type this)) Long (impl [this] (type this))) (impl "test") ;=> java.lang.String (impl :test) ;=> clojure.lang.Keyword (impl 1) ;=> java.lang.Long
(defprotocol Fly (fly [this] "Method to fly")) (defrecord Bird [name species] Fly ;상속 (fly [this] (str (:name this) " flies..."))) (extends? Fly Bird) ;=> true (def crow (Bird. "Crow" "Black!")) (fly crow) ;=> "Crow flies..."
(defrecord Person [name age]) (def hyuk (Person. "hyuk" 30)) (.-name hyuk) => "hyuk" (.-age hyuk) => 30
(defprotocol Action (eat [this]) (said [this])) (defrecord Person [name age] Action (eat [this] (str name " eat somthing...")) (said [this] (str "My age is " age))) (def hyuk (Person. "hyuk" 30)) (.-name hyuk) ;=> "hyuk" (.-age hyuk) ;=> 30 (eat hyuk) ;=> "hyuk eat somthing..." (said hyuk) ;=> "My age is 30"
(defprotocol Action (eat [this]) (said [this])) (deftype Person [] Action (eat [this] (str "Somebody eat somthing...")) (said [this] (str "My age is secret."))) (def somebody (Person.)) (eat somebody) ;=> "Somebody eat somthing..." (said somebody) ;=> "My age is secret."
코드 재배열을 하여 가독성을 높이는 매크로
https://clojuredocs.org/clojure.core/-%3E
(get (.split (.toUpperCase "a b c d") " ") 1) (-> "a b c d" .toUpperCase (.split " ") (get 1)) => "B"
(second (next [1 2 3 4 5])) (-> [1 2 3 4 5] next second) => 3
https://clojuredocs.org/clojure.core/-%3E%3E
(take 3 (filter odd? (range))) (->> (range) (filter odd?) (take 3)) => (1 3 5)
;현재 namespace 사용 (ns-unmap *ns* 'var) ;직접 "user"라는 namespace 지정 (ns-unmap (find-ns 'user) 'var)
(-> % .-target .-value)