Clojure
Scala
ClojureDay 24

ScalaとClojureをまぜまぜしてみる

これはClojure Advent Calendar 2017の24日の記事です。

Java ⇔ Clojure、あるいはJava ⇔ Scalaの相互運用性に関する情報はよく見かけますが、Clojure ⇔ Scalaはいったいどんな感じになるのだろうと思い調べてみました。

ScalaプロジェクトにClojureをまぜまぜしてみる

sbt-clojureをplugins.sbtに追加

まずClojureコードをコンパイルするsbtプラグインであるsbt-clojureproject/plugins.sbtに追加します。

project/plugins.sbt
addSbtPlugin("com.unhandledexpression" % "sbt-clojure" % "0.1")

次にClojure本体を依存関係に追加 & sbt-clojureをプロジェクトで利用できるように設定します。

build.sbt
libraryDependencies += "org.clojure" % "clojure" % "1.9.0"

clojure.settings

なおsbt-clojureのドキュメントではseq(clojure.settings :_*)のようにシーケンスを可変長数に展開してからseqに渡すようになっていますが、最近のsbtではこのようにする必要はありません。clojure.settingsだけでOKです。

これで準備は完了です。

Clojureを書く

続いてScalaプロジェクト内で使うClojureの関数を書いていきます。
下記は渡された名前から挨拶を組み立てて返すだけの関数です。

greet.clj
(ns greet
  (:gen-class
   :methods [#^{:static true} [hello [String] String]]))

(defn -hello [name] (str "Hello " name " from Clojure!"))

:gen-classはコンパイル時にクラスのバイトコードを生成するために必要です。
:methodsでは生成されるメソッドの名前や引数と返り値のシグネチャを指定します。
gen-classは「接頭辞 + :methodsで定義された名前」の関数をメソッドの実装として生成します。 (defn -hello []...)と関数名が-で始まるのはデフォルトの接頭辞が-であるためです。

ScalaコードからClojureの関数を呼ぶ

こちらは特に難しいことはありません。

Main.scala
object Main extends App {

  println(greet.hello("todokr"))
}

$ sbt runすると無事ScalaからClojureが呼べていることが分かります。

~/g/clojure-with-scala ❯❯❯ sbt run
Hello todokr from Clojure!
[success] Total time: 6 s, completed 2017/12/24 3:56:13

プロジェクトの構成は下記のようになります。

.
├── build.sbt
├── project
│   └── plugins.sbt
└── src
    └── main
        ├── clojure
        │   └── greet.clj
        └── scala
            └── Main.scala

ClojureにScalaをまぜまぜしてみる

逆に、ClojureプロジェクトからScalaのコードを呼んでみましょう。
今回はzincを使ったコンパイルをleiningenから行うlein-zincを使ってみます。
zincはsbt内部で使われているコンパイラのスタンドアロン版で、インクリメンタルで高速なコンパイルが特徴です。

leiningenから適当にプロジェクトを生成したら、project.cljに下記を追加します。

project.clj
:prep-tasks ["zinc" "compile"]
:plugins [[lein-zinc "1.2.0"]]
:source-paths ["src/clojure"]
:java-source-paths ["src/scala"]

Scalaを書く

Clojureから呼ばれるScalaのメソッドを書きます。やっていることは先程のClojure版と同じですね。

package scala

object Greet {

  def sayHello(name: String) = s"Hello $name from Scala!"
}

ClojureからScalaのメソッドを呼ぶ

:importマクロを使い、JavaのAPIを呼ぶのと同じ要領で呼んでやります。

(ns core
  (:import (scala Greet))
  (:gen-class))

(defn -main []
  (println (Greet/sayHello "todokr")))

$ lein runを実行するとClojureからScalaのメソッドを呼べていることが分かります。

~/g/scala-with-clojure ❯❯❯ lein run
scala version:  2.12.0
sbt   version:  0.13.9
fork java?      false
Hello todokr from Scala!

プロジェクト構成は下記の通りです。

.
├── project.clj
└── src
    ├── clojure
    │ └── core.clj
    └── scala
         └── Greet.scala

所感

やれなくはない。が、各言語のメリットを引き出すには使いどころを結構吟味する必要がありそうです。引き続き研究していきたいと思います。

ラストは@totakkeさんです!