Help us understand the problem. What is going on with this article?

RubyistのためのClojure入門

More than 5 years have passed since last update.

PuppetRubyからClojureへ乗り換えていくようです。

そんなRubyistが他にもいるかもしれないので、Rubyist向けにClojureだとどう書くか、みたいなガイドを書いてみます。

イディオム

jnchitoさんのRubyイディオム記事をClojureで書くとどうなるか、まず説明したいと思います。

後置if で行数を減らす

ruby
send_mail_to(user) if user.active?

いきなりですが、Clojureには後置記法がありません。

clojure
(if (:active? user)
  (send-mail-to user))

慣例的な書き方では、おしりの括弧は改行入れずにまとめて書くので、他の言語のifブロックよりも1行少なくなります。

if + notではなく、unlessを使う

ruby
user.destroy unless user.active?

Clojureにはこの目的の、if-notwhen-notがあります。

clojure
(if-not (:active? user)
  (destroy user))

どうしてもunlessキーワードじゃなきゃヤダというのであれば、マクロを使ってunless構文を作ることができます。

clojure
(defmacro unless [condition success-body & failure-body]
  `(if-not ~condition ~success-body ~failure-body))

(unless (:active? user)
  (destroy user))

三項演算子を使って行数を減らす

ruby
user.admin? ? "I appreciate for that." : "Thanks"

そもそも多項演算子がデフォなので、「こういう場合は三項演算子を使わない」とかそういう議論が要りません。

clojure
(if (:admin? user) "I appreciate for that." "Thanks.")

if文もただのリストなので、もとより三項演算子っぽいです。

代入してからifで存在を確認、をまとめて書く

ruby
if user = find_user
  send_mail_to(user)
end

if-letは値が束縛できれば、与えられた式を評価する関数です。まったくもって上記のrubyのコードを同じことが、スマートに実現できます。

clojure
(if-let [user (find-user)]
  (send-mail-to user))

子どものオブジェクトが存在する場合にのみ、そのプロパティやメソッドを呼び出して条件を確認する、をひとつのifで書く

ruby
if parent.children && parent.children.singleton?
  singleton = parent.children.first
  send_mail_to(singleton)
end

Clojureのスレッディングマクロsome->は与えられた式を順に評価しnilになるとそこで停止するものです。Optionalを使ってやりたいであろうことが、このスレッディングマクロを使うとOptionalという概念を持ち込まずとも実現できます。

clojure
(if (some-> parent (.children) (.singleton?))
  (send-mail-to (.. parent children first)))

メソッドの戻り値を返すときにreturnを使わない

ruby
def build_message(user)
  message = 'hello'
  message += '!!' if user.admin?
  message
end

これはClojureの場合当たり前ですが、returnという概念がないので使えません。rubyと同じく、関数を評価した結果が返ります。

clojure
(defn build-message [user]
  (str "hello" (when (:admin? user) "!!")))

定数はfreezeさせる

ruby
ADMIN_NAMES = ["Tom", "Alice"].freeze
ADMIN_NAMES << "Taro" # => RuntimeError: can't modify frozen Array

これはClojureの強みです。値は決して変更できないので、freezeするという概念がありません。

clojure
=> (def admin-names ["Tom" "Alice"])
#'user/admin-names
=> (conj admin-names "Taro")
["Tom" "Alice" "Taro"]
=> admin-names
["Tom" "Alice"]

このように、admin-namesに一度束縛した値はCollectionであっても変更はできません。変更しようとすると別の値が作られるだけです。

配列を作るとき、[ ]の代わりに%w( )、%i( )を使う

ruby
actions = %w(index new create)

残念ながらClojureにはこの機能はありません。しかしマクロがあります。

clojure
(defmacro %w [& words]
  (vec (for [w words] (str w))))

(%w hoge fuga piyo)
=> ["hoge" "huga" "piyo"]

(あ、これはジョークです)

要素の順番に意味がある配列は、同時に別々の変数で受け取る

ruby
quotient, remainder = 14.divmod(3)
puts "商は#{quotient}"      # => 商は4
puts "あまりは#{remainder}" # => あまりは2

Clojureにはこの目的で、デストラクティング構文があります。

clojure
(let [[quotient remainder] (divmod 14 3)]
  (puts "商は" quotient)
  (puts "あまりは" remainder))

これは強力で、複雑なデータ構造でも1回のletでそれぞれの変数に束縛できます。

clojure
(let [[[x1 y1][x2 y2]] [[1 2] [3 4]]]
  [x1 y1 x2 y2])

=>[1 2 3 4]

Webフレームワーク

Railsのようなフルスタックなフレームワークはあまり流行してません。
Sinatra相当のcompojureと、Rack相当のringがメジャーです。

compojureに関しては、そこそこ記事もあります。http://qiita.com/search?q=compojure

compojure
(GET "/user/:id" [id]
  (str "<h1>Hello user " id "</h1>"))

テンプレート

Rubyの主要テンプレートライブラリであるhamlは、とてもS式です。

haml
!!!
%html{ :xmlns => "http://www.w3.org/1999/xhtml", :lang => "en", "xml:lang" => "en"}
  %head
    %title BoBlog
    %meta{"http-equiv" => "Content-Type", :content => "text/html; charset=utf-8"}
    %link{"rel" => "stylesheet", "href" => "main.css", "type" => "text/css"}
  %body
    #header
      %h1 BoBlog
      %h2 Bob's Blog
    #content
      - @entries.each do |entry|
        .entry
          %h3.title= entry.title
          %p.date= entry.posted.strftime("%A, %B %d, %Y")
          %p.body= entry.body
    #footer
      %p
        All content copyright © Bob

これをClojureのもっともメジャーなHTML出力ライブラリであるhiccupを使うと、以下のように書けます。

hiccup
(html5 {:xmlns "http://www.w3.org/199/xhtml" :lang "en" "xml:lang" "en"}
  [:head
    [:title "BoBlog"]
    [:meta {:http-equiv "Content-Type" :content "text/html; charset=utf-8"}]
    [:link {:rel "stylesheet" :href "main.css" :type "text/css"}]]
  [:body
    [:div#header
      [:h1 "BoBlog"]
      [:h2 "Bob's Blog"]]
    [:div#content
      (for [entry entries]
        [:div.entry
          [:h3.title (:title entry)]
          [:p.date   (:posted entry)]
          [:p.body   (strftime "%A, %B %d, %Y" (:body entry]]))]])]

同じですね。それでいてhiccupはClojureの式そのものなので思考の切り替えなく、業務ロジックとHTML出力を書くことができます。

お誘い

さて、少しでもClojureに興味を持っていただけたRubyistのみなさんに、ピッタリのイベントがあります。

10/10 19:00〜 Clojure夜会
http://01e8c979c4e57f83dd63bf3d4a.doorkeeper.jp/events/14626

ライトなClojureの話題が中心ですので、参加してみてください!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした