#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)