Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active November 11, 2022 07:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lagenorhynque/e9ac23f2695586a28d2eb59757f3455c to your computer and use it in GitHub Desktop.
Save lagenorhynque/e9ac23f2695586a28d2eb59757f3455c to your computer and use it in GitHub Desktop.
JavaからClojureへ - Adieu Java -

JavaからClojureへ

- Adieu Java -


自己紹介


lagénorhynque /laʒenɔʁɛ̃k/

(defprofile lagénorhynque
  :name       "Kent OHASHI"
  :account    @lagenorhynque
  :company    株式会社オプト
  :languages  [Clojure Haskell Python Scala
               English français Deutsch русский]
  :interests  [プログラミング 語学 数学])

twitter icon


Lisp × Java

Lisp Alien


言語としてのJava


言語としてのJavaの問題点


  • 関数型プログラミング(FP)サポートの不足

    • もっとFPしたい! (OOP要素も静的型付けも必須ではない)
  • 冗長なシンタックス

    • もっとシンプルに書きたい!
  • 柔軟性/拡張性の不足

    • もっと自由に書きたい!

独断と偏見によるJVM言語比較


factor Java Groovy Scala Kotlin Clojure
FP support ×
simplicity ×
flexibility ×

Clojure を使えば、

言語としての Java とはお別れできる!!


Adieu, Java !

Javaともこれで本当にお別れだね(;_;)/~~~


What is Clojure?


Clojureの名前の由来


Clojure is pronounced exactly like closure, where the s/j has the zh sound as in azure, pleasure etc.

The name was chosen to be unique. I wanted to involve c (c#), l (lisp) and j (java).

Once I came up with Clojure, given the pun on closure, the available domains and vast emptiness of the googlespace, it was an easy decision.

― Rich Hickey, creator of Clojure

cf. meaning and pronunciation of Clojure


Clojure /ˈkloʊʒɚ/

※ NOT /ˈkloʊd͡ʒɚ/


element meaning
/ˈkloʊʒɚ/ クロージャ(closure), 関数型プログラミング
C C#(.NET) as a platform, .NET言語
l Lisp方言
j Java as a platform, JVM言語

  1. FP言語としてのClojure

  2. Lisp方言としてのClojure

  3. JVM言語としてのClojure


FP言語としてのClojure


イミュータブルなList, Vector, Map, Set, etc.


user=> '(1 2 3)
(1 2 3)

user=> [1 2 3]
[1 2 3]

user=> {:a 1 :b 2 :c 3}
{:a 1, :b 2, :c 3}

user=> #{1 2 3}
#{1 3 2}

高階関数(filter, map, reduce, etc.)


user=> (def xs [1 2 3])
#'user/xs

user=> (filter odd? xs)
(1 3)

user=> (map #(* % %) xs)
(1 4 9)

user=> (reduce + 0 xs)
6

user=> (reduce + 0 (map #(* % %) (filter odd? xs)))
10

user=> (->> xs
  #_=>      (filter odd?)
  #_=>      (map #(* % %))
  #_=>      (reduce + 0))
10

ちなみに、


#( )

#(* % %)

↓↓↓

(fn [x] (* x x))

に相当するリーダマクロ


ちなみに、


->>

(->> a
     (f x)
     (g y)
     (h z))

↓↓↓

(h z (g y (f x a)))

に展開されるマクロ(threading macroの一種)


遅延シーケンス


user=> (def nats (iterate inc 0))
#'user/nats

user=> (take 10 nats)
(0 1 2 3 4 5 6 7 8 9)

user=> (take-while #(< % 10) nats)
(0 1 2 3 4 5 6 7 8 9)

Lisp方言としてのClojure



(f a b c ...)

  • f: 関数, マクロ, 特殊形式
  • a, b, c, ...: 引数

cf. Java

f(a, b, c, ...)


関数/メソッドの定義も

// Java
public void greet(String name) {
    System.out.println("Bonjour, " + name + " !");
}

S式

;; Clojure
(defn greet [name]
  (println (str "Bonjour, " name " !")))

名前空間/パッケージの宣言もインポートも

// Java
package demo_app;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

S式

;; Clojure
(ns demo-app.core
  (:import (java.io IOException)
           (java.util ArrayList List)))

first(≒ car), rest(≒ cdr), cons, ...


user=> (def xs [1 2 3])
#'user/xs

user=> (first xs)
1

user=> (rest xs)
(2 3)

user=> (cons 0 xs)
(0 1 2 3)

S式のコードはそのままデータとして扱える

(code as data; homoiconicity)


user=> (first '(def xs [1 2 3]))
def

user=> (rest '(def xs [1 2 3]))
(xs [1 2 3])

user=> (cons 'def '(xs [1 2 3]))
(def xs [1 2 3])

user=> (eval (cons 'def '(xs [1 2 3])))
#'user/xs

Lispマクロ

強力なコンパイル時メタプログラミング機構


user=> (defmacro unless  ; clojure.core/if-not マクロを再実装しただけ
  #_=>   ([test then]
  #_=>    `(unless ~test ~then nil))
  #_=>   ([test then else]
  #_=>    `(if (not ~test)
  #_=>       ~then
  #_=>       ~else)))
#'user/unless

user=> (unless (= 1 2) :ok)
:ok

user=> (macroexpand '(unless (= 1 2) :ok))
(if (clojure.core/not (= 1 2)) :ok nil)

ちなみに、


関数定義の defn

user=> (macroexpand
  #_=>  '(defn greet [name]
  #_=>     (println (str "Bonjour, " name " !"))))
(def greet (clojure.core/fn ([name] (println (str "Bonjour, " name " !")))))

特殊形式 def, fn を利用したマクロ


大量の括弧を扱うのがつらい?


⇒ Lisp編集用プラグインがあれば非常に快適


JVM言語としてのClojure


Javaのclassファイルにコンパイル

jarとして実行可能


$ lein new app demo-app
Generating a project called demo-app based on the 'app' template.

$ cd demo-app/

$ lein uberjar
Compiling demo-app.core
Created /Users/lagenorhynchus/code/demo-app/target/uberjar/demo-app-0.1.0-SNAPSHOT.jar
Created /Users/lagenorhynchus/code/demo-app/target/uberjar/demo-app-0.1.0-SNAPSHOT-standalone.jar

$ java -jar target/uberjar/demo-app-0.1.0-SNAPSHOT-standalone.jar
Hello, World!

Javaメソッドの呼び出し


staticメソッド

// Java
Integer.parseInt("123")
;; Clojure
(Integer/parseInt "123")

インスタンスメソッド

// Java
"a b c".split("\\s")
;; Clojure
(.split "a b c" "\\s")

破壊的な初期化/設定

// Java
java.util.Map<String, Integer> m = new java.util.HashMap<>();
m.put("a", 1);
m.put("b", 2);
m.put("c", 3);
return m;
;; Clojure
(doto (java.util.HashMap.)
  (.put "a" 1)
  (.put "b" 2)
  (.put "c" 3))

メソッドチェーン

// Java
new StringBuilder()
    .append("a")
    .append("b")
    .append("c")
    .toString()
;; Clojure
(.. (StringBuilder.)
    (append "a")
    (append "b")
    (append "c")
    toString)
;; または
(-> (StringBuilder.)
    (.append "a")
    (.append "b")
    (.append "c")
    .toString)

JavaコレクションAPIとの連携


Javaのコレクション → Clojureの関数

user=> (def xs (doto (java.util.ArrayList.)
  #_=>           (.add 1)
  #_=>           (.add 2)
  #_=>           (.add 3)))
#'user/xs

user=> (class xs)
java.util.ArrayList

user=> xs
[1 2 3]

user=> (map inc xs)
(2 3 4)

Clojureのコレクション → Javaのメソッド

user=> (def xs [1 2 3 4 5])
#'user/xs

user=> (class xs)
clojure.lang.PersistentVector

user=> (instance? java.util.List xs)
true

user=> (.subList xs 1 4)
[2 3 4]

cf. Google Sheets API (Java版)の利用例

// Java
public Integer duplicateWorksheet(Sheets sheets, String spreadsheetId, Integer worksheetId, String worksheetName) {
    List<Request> reqs = Arrays.asList(
        new Request().setDuplicateSheet(
            new DuplicateSheetRequest().setSourceSheetId(worksheetId)
                .setNewSheetName(worksheetName)
                .setInsertSheetIndex(1)
        )
    );
    return executeUpdate(sheets, spreadsheetId, worksheetId, reqs)
        .getReplies()
        .get(0)
        .getDuplicateSheet()
        .getProperties()
        .getSheetId();
}
;; Clojure
(defn duplicate-worksheet [sheets spreadsheet-id worksheet-id worksheet-name]
  (let [reqs [(-> (Request.)
                  (.setDuplicateSheet
                   (-> (DuplicateSheetRequest.)
                       (.setSourceSheetId worksheet-id)
                       (.setNewSheetName worksheet-name)
                       (.setInsertSheetIndex (int 1)))))]]
    (-> (execute-update sheets spreadsheet-id worksheet-id reqs)
        .getReplies
        first
        .getDuplicateSheet
        .getProperties
        .getSheetId)))

ほかにも


  • core.async

  • transducers

  • clojure.spec

  • ClojureScript

etc.


Clojure なら

FPとLispとJavaの力を引き出して

もっとシンプルに、もっと自由に、プログラミングできる!!


Vive les S-expressions !

Lisp Alien

S式万歳!


Further Reading


From Java To Clojure

- Adieu Java -


Self-introduction


lagénorhynque /laʒenɔʁɛ̃k/

(defprofile lagénorhynque
  :name       "Kent OHASHI"
  :account    @lagenorhynque
  :company    "Opt, Inc."
  :languages  [Clojure Haskell Python Scala
               English français Deutsch русский]
  :interests  [programming language-learning mathematics])

twitter icon


Lisp × Java

Lisp Alien


Java as a language


Problems with Java as a language


  • lack of functional programming (FP) support

    • More FP! (OOP and static typing are not necessary)
  • verbose syntax

    • More simplicity!
  • lack of flexibility/extensibility

    • More freedom!

JVM language comparison

from my own point of view


factor Java Groovy Scala Kotlin Clojure
FP support ×
simplicity ×
flexibility ×

Using Clojure,

we can say goodbye to Java as a language!!


Adieu, Java !

Now we have to say farewell to you, Java (;_;)/~~~


What is Clojure?


Origin of the name Clojure


Clojure is pronounced exactly like closure, where the s/j has the zh sound as in azure, pleasure etc.

The name was chosen to be unique. I wanted to involve c (c#), l (lisp) and j (java).

Once I came up with Clojure, given the pun on closure, the available domains and vast emptiness of the googlespace, it was an easy decision.

― Rich Hickey, creator of Clojure

cf. meaning and pronunciation of Clojure


Clojure /ˈkloʊʒɚ/

* NOT /ˈkloʊd͡ʒɚ/


element meaning
/ˈkloʊʒɚ/ closure, functional programming
C C#(.NET) as a platform, .NET language
l Lisp dialect
j Java as a platform, JVM language

  1. Clojure as a FP language

  2. Clojure as a Lisp dialect

  3. Clojure as a JVM language


Clojure as a FP language


Immutable List, Vector, Map, Set, etc.


user=> '(1 2 3)
(1 2 3)

user=> [1 2 3]
[1 2 3]

user=> {:a 1 :b 2 :c 3}
{:a 1, :b 2, :c 3}

user=> #{1 2 3}
#{1 3 2}

Higher-order functions

(filter, map, reduce, etc.)


user=> (def xs [1 2 3])
#'user/xs

user=> (filter odd? xs)
(1 3)

user=> (map #(* % %) xs)
(1 4 9)

user=> (reduce + 0 xs)
6

user=> (reduce + 0 (map #(* % %) (filter odd? xs)))
10

user=> (->> xs
  #_=>      (filter odd?)
  #_=>      (map #(* % %))
  #_=>      (reduce + 0))
10

BTW,


#( ) is

#(* % %)

↓↓↓

(fn [x] (* x x))

a reader macro equivalent as above.


BTW,


->> is

(->> a
     (f x)
     (g y)
     (h z))

↓↓↓

(h z (g y (f x a)))

a macro (a kind of threading macros) expanded as above.


Lazy sequences


user=> (def nats (iterate inc 0))
#'user/nats

user=> (take 10 nats)
(0 1 2 3 4 5 6 7 8 9)

user=> (take-while #(< % 10) nats)
(0 1 2 3 4 5 6 7 8 9)

Clojure as a Lisp dialect



(f a b c ...)

  • f: function, macro, special form
  • a, b, c, ...: arguments

cf. Java

f(a, b, c, ...)


Even a function/method definition

// Java
public void greet(String name) {
    System.out.println("Bonjour, " + name + " !");
}

is an S-expression.

;; Clojure
(defn greet [name]
  (println (str "Bonjour, " name " !")))

Even namespace/package declaration and imports

// Java
package demo_app;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

are S-expressions.

;; Clojure
(ns demo-app.core
  (:import (java.io IOException)
           (java.util ArrayList List)))

first(≒ car), rest(≒ cdr), cons, ...


user=> (def xs [1 2 3])
#'user/xs

user=> (first xs)
1

user=> (rest xs)
(2 3)

user=> (cons 0 xs)
(0 1 2 3)

S-expression code can be treated as data

(code as data; homoiconicity)


user=> (first '(def xs [1 2 3]))
def

user=> (rest '(def xs [1 2 3]))
(xs [1 2 3])

user=> (cons 'def '(xs [1 2 3]))
(def xs [1 2 3])

user=> (eval (cons 'def '(xs [1 2 3])))
#'user/xs

Lisp macros

powerful compile-time metaprogramming facility


user=> (defmacro unless  ; just a reimplementation of clojure.core/if-not macro
  #_=>   ([test then]
  #_=>    `(unless ~test ~then nil))
  #_=>   ([test then else]
  #_=>    `(if (not ~test)
  #_=>       ~then
  #_=>       ~else)))
#'user/unless

user=> (unless (= 1 2) :ok)
:ok

user=> (macroexpand '(unless (= 1 2) :ok))
(if (clojure.core/not (= 1 2)) :ok nil)

BTW,


defn used for function definition

user=> (macroexpand
  #_=>  '(defn greet [name]
  #_=>     (println (str "Bonjour, " name " !"))))
(def greet (clojure.core/fn ([name] (println (str "Bonjour, " name " !")))))

is a macro composed of def, fn special forms.


Hard to handle a lot of parentheses?


⇒ Lisp-editing plugins make it very comfortable


Clojure as a JVM language


Compiling to Java class files

executable as a jar


$ lein new app demo-app
Generating a project called demo-app based on the 'app' template.

$ cd demo-app/

$ lein uberjar
Compiling demo-app.core
Created /Users/lagenorhynchus/code/demo-app/target/uberjar/demo-app-0.1.0-SNAPSHOT.jar
Created /Users/lagenorhynchus/code/demo-app/target/uberjar/demo-app-0.1.0-SNAPSHOT-standalone.jar

$ java -jar target/uberjar/demo-app-0.1.0-SNAPSHOT-standalone.jar
Hello, World!

Calling Java methods


static methods

// Java
Integer.parseInt("123")
;; Clojure
(Integer/parseInt "123")

instance methods

// Java
"a b c".split("\\s")
;; Clojure
(.split "a b c" "\\s")

destructive initialisation/setting

// Java
java.util.Map<String, Integer> m = new java.util.HashMap<>();
m.put("a", 1);
m.put("b", 2);
m.put("c", 3);
return m;
;; Clojure
(doto (java.util.HashMap.)
  (.put "a" 1)
  (.put "b" 2)
  (.put "c" 3))

method chaining

// Java
new StringBuilder()
    .append("a")
    .append("b")
    .append("c")
    .toString()
;; Clojure
(.. (StringBuilder.)
    (append "a")
    (append "b")
    (append "c")
    toString)
;; or
(-> (StringBuilder.)
    (.append "a")
    (.append "b")
    (.append "c")
    .toString)

Interoperating with Java collection API


Java collection → Clojure function

user=> (def xs (doto (java.util.ArrayList.)
  #_=>           (.add 1)
  #_=>           (.add 2)
  #_=>           (.add 3)))
#'user/xs

user=> (class xs)
java.util.ArrayList

user=> xs
[1 2 3]

user=> (map inc xs)
(2 3 4)

Clojure collection → Java method

user=> (def xs [1 2 3 4 5])
#'user/xs

user=> (class xs)
clojure.lang.PersistentVector

user=> (instance? java.util.List xs)
true

user=> (.subList xs 1 4)
[2 3 4]

cf. usage example of Google Sheets API (Java)

// Java
public Integer duplicateWorksheet(Sheets sheets, String spreadsheetId, Integer worksheetId, String worksheetName) {
    List<Request> reqs = Arrays.asList(
        new Request().setDuplicateSheet(
            new DuplicateSheetRequest().setSourceSheetId(worksheetId)
                .setNewSheetName(worksheetName)
                .setInsertSheetIndex(1)
        )
    );
    return executeUpdate(sheets, spreadsheetId, worksheetId, reqs)
        .getReplies()
        .get(0)
        .getDuplicateSheet()
        .getProperties()
        .getSheetId();
}
;; Clojure
(defn duplicate-worksheet [sheets spreadsheet-id worksheet-id worksheet-name]
  (let [reqs [(-> (Request.)
                  (.setDuplicateSheet
                   (-> (DuplicateSheetRequest.)
                       (.setSourceSheetId worksheet-id)
                       (.setNewSheetName worksheet-name)
                       (.setInsertSheetIndex (int 1)))))]]
    (-> (execute-update sheets spreadsheet-id worksheet-id reqs)
        .getReplies
        first
        .getDuplicateSheet
        .getProperties
        .getSheetId)))

Furthermore,


etc.


If you use Clojure,

with the power of FP, Lisp and Java

you can program more simply, more freely!!


Vive les S-expressions !

Lisp Alien

Long live S-expressions!


Further Reading


(ns release-reporter.spreadsheet
(:require [environ.core :refer [env]]
[google-apps-clj.credentials :as cred]
[google-apps-clj.google-sheets-v4 :as sheets4])
(:import (com.google.api.client.googleapis.javanet GoogleNetHttpTransport)
(com.google.api.client.json.jackson2 JacksonFactory)
(com.google.api.services.sheets.v4 Sheets$Builder)
(com.google.api.services.sheets.v4.model BatchUpdateSpreadsheetRequest
DuplicateSheetRequest
GridCoordinate
Request
UpdateCellsRequest)))
;; Google Sheets API
(def target-spreadsheet {:stg (:target-spreadsheet-stg env)
:prod (:target-spreadsheet-prod env)})
(def template-worksheet {:stg (Integer/parseInt (:template-worksheet-stg env))
:prod (Integer/parseInt (:template-worksheet-prod env))})
(def google-ctx (clojure.edn/read-string (slurp "config/google-creds.edn")))
(def start-index {:row (int 2)
:column (int 1)})
(defn fetch-cell-value [sheets spreadsheet-id position]
(let [worksheet-name (-> sheets
.spreadsheets
(.get spreadsheet-id)
.execute
.getSheets
fnext
.getProperties
.getTitle)]
(-> sheets
.spreadsheets
.values
(.get spreadsheet-id (str worksheet-name \! position))
.execute
.getValues
ffirst)))
(defn execute-update [sheets spreadsheet-id worksheet-id requests]
(let [worksheet-id (int worksheet-id)]
(-> sheets
.spreadsheets
(.batchUpdate
spreadsheet-id
(-> (BatchUpdateSpreadsheetRequest.)
(.setRequests requests)))
.execute)))
(defn duplicate-worksheet [sheets spreadsheet-id worksheet-id worksheet-name]
(let [reqs [(-> (Request.)
(.setDuplicateSheet
(-> (DuplicateSheetRequest.)
(.setSourceSheetId worksheet-id)
(.setNewSheetName worksheet-name)
(.setInsertSheetIndex (int 1)))))]]
(-> (execute-update sheets spreadsheet-id worksheet-id reqs)
.getReplies
first
.getDuplicateSheet
.getProperties
.getSheetId)))
(defn update-rows [sheets spreadsheet-id worksheet-id rows]
(let [reqs [(-> (Request.)
(.setUpdateCells
(-> (UpdateCellsRequest.)
(.setStart (-> (GridCoordinate.)
(.setSheetId worksheet-id)
(.setRowIndex (:row start-index))
(.setColumnIndex (:column start-index))))
(.setRows (map sheets4/row->row-data rows))
(.setFields "userEnteredValue"))))]]
(execute-update sheets spreadsheet-id worksheet-id reqs)))
(defmacro with-sheets [& body]
`(let [~'sheets (-> (Sheets$Builder.
(GoogleNetHttpTransport/newTrustedTransport)
(JacksonFactory/getDefaultInstance)
(cred/build-credential google-ctx))
(.setApplicationName "release reporter")
.build)]
~@body))
#!/usr/bin/env bash
# npm install -g reveal-md@0.0.32
reveal-md from-java-to-clojure.md --theme night --highlightTheme monokai-sublime
#!/usr/bin/env bash
# npm install -g reveal-md@0.0.32
reveal-md from-java-to-clojure_en.md --theme night --highlightTheme monokai-sublime
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment