LoginSignup
27
19

More than 5 years have passed since last update.

PythonistaのためのLisp入門(Hyチュートリアル和訳)

Last updated at Posted at 2017-06-04

この記事はHy Tutorialの和訳です.おかしいところなどございましたらご指摘いただけると幸いです.

読み進める前に

訳だけ丸投げもどうかと思ったので追記です.

準備編

PythonおよびHyをインストールして手許で確認しながら読み進めたほうが理解が速いかと思います.
Pythonの設定は各環境で異なりますので頑張ってください.とりあえず動かすだけならMacや大抵のLinuxディストリビューションには端からバンドルされていますのでそれで構いません.
HyはGithubから最新版をダウンロードしておきます.管理者権限で以下を実行してください.

$ pip install git+https://github.com/hylang/hy

前提知識編

文中のPythonのコードはすべてPython2準拠です.Pythonには2.x系と3.x系の二系統が有りますが一部コードの互換性がありません.お使いのPythonが3.x系の場合は以下の記事などを参照に上手く読み替えてください.

例の中で以下の表現が出てきた場合はPythonのREPLで実行していることを意味します.

>>> print "これはPythonのREPLです"

同様に以下の表現はHyのREPLで実行していることを表します.

=> (print "これはHyのREPLです")

それでは,蛇足はここまでで以下本文です.

チュートリアル

Hyのチュートリルへようこそ!一言で言うならば,HyはLisp方言だ.ただしHyはその構造をPythonに変換できる.文字通りPyhonの抽象構文木へと変換するのだ.(雑に言い換えればHyはPython上のlisp-stickだ.)このことはかなりクールだ.なぜならHyには様々な捉え方があることを意味するからだ.

  • すごくPython風味なLisp
  • Lisp使いたちにとって: LispのメチャクチャなパワーをPythonの充実したライブラリと共に使うための手段(何が良いかって,DjangoアプリをLispで書けるんだぜ!)
  • Python使いたちにとって: 快適なPythonからLisp探訪へ旅立つのに良いやり方
  • すべての人々にとって: 冴えてるアイデアだらけの楽しい言語

Pythonistaのための基礎

今までにLispを書いたことなくてもPython使ってるなら大丈夫だ.Hyの「hello world」は本当にシンプルだ.試しにやってみよう.

(print "hello world")

分かっただろう?簡単なことだ.君の思っている通りこれはPythonで書けばこうだ.

print "hello world"

ごく単純な算数をやってみよう.こんな感じだ.

(+ 1 3)

戻り値は4だ.これは次のコードに相当する.

1 + 3

ここで注意しなきゃいけないのは,リストの始めの要素は関数として呼び出されるということと,残りが引数としてその関数に渡されるということだ.Hyでは(だいたいのLispでは)+演算子に複数の引数を渡せるようになっている.

(+ 1 3 55)

戻り値は59だ.
もしかしたらLispのことを聞いたことはあっても良くは知らなかったかもしれない.Lispは君が考えているほど難しくはない.明らかにLispの括弧は多い.最初はこれに戸惑うかもしれないが大したことはない.括弧の束で包まれた簡単な算数をインタプリタで試してみよう.

(setv result (- (/ (+ 1 3 88) 2) 8))

38が返ってくる.なぜだろうか?一旦Pythonで同様の表現を見てみよう.

result = ((1 + 3 + 88) / 2) - 8

上記のコードがPythonでどうやって動くか確かめたいならば,当たり前だがそれぞれの括弧の中を計算すれば理解できる.これはHyの基本的な考え方と同じだ.まずPythonでやってみよう.

result = ((1 + 3 + 88) / 2) - 8
# こうなって...
result = (92 / 2) - 8
# こうなって...
result = 46 - 8
# こうなる...
result = 38

次にHyでやってみよう.

(setv result (- (/ (+ 1 3 88) 2) 8))
; こうなって...
(setv result (- (/ 92 2) 8))
; こうなって...
(setv result (- 46 8))
; こうなる...
(setv result 38)

お気づきかもしれないが,最後のsetvを使った表現は変数resultに38を割り当てるものだ.どうだろうか?それほど難しくはないだろう.

これはLispの大前提だが,「Lisp」とは「lisp processing(リスト処理)」を意味する.実際,Lispプログラムの構造はリストのリストなのだ.(もしPythonのリストに慣れているなら同様の構造を角括弧で置き換えて考えてみて欲しい.データ構造にも見えるし,プログラムにも見えるだろう.)このことはもっと例を上げればより理解しやすいだろう.ここからはシンプルなPythonプログラムを試したうえで等価なHyプログラムをお見せしよう.

def simple_conversation():
    print "Hello!  I'd like to get to know you.  Tell me about yourself!"
    name = raw_input("What is your name? ")
    age = raw_input("What is your age? ")
    print "Hello " + name + "!  I see you are " + age + " years old."

simple_conversation()

上記のコードを実行すると以下のようになる.

Hello!  I'd like to get to know you.  Tell me about yourself!
What is your name? Gary
What is your age? 38
Hello Gary!  I see you are 38 years old.

Hyで同じことをやるとこうだ.

(defn simple-conversation []
   (print "Hello!  I'd like to get to know you.  Tell me about yourself!")
   (setv name (raw-input "What is your name? "))
   (setv age (raw-input "What is your age? "))
   (print (+ "Hello " name "!  I see you are "
              age " years old.")))

(simple-conversation)

各リストの先頭の要素が関数(あるいはマクロだが後で説明しよう)で,残りが引数であるということを覚えていたなら簡単に理解できるはずだ.(お気づきかもしれないがdefnはメソッドを定義するためのHyのメソッドだ.)

それでも多くの括弧に混乱する人は多いだろう.簡単のためには様々な方法がある.例えば,インデントを綺麗に保ち括弧の対応ができるエディタを使うことだ.そうすれば快適に感じられるようになるだろう.

Lispの中核をなすデータ構造のように,コードの構造をごく単純なものにするといくつか良いことがある.一つはコードがパースしやすく,実際の構造全体を明確に見渡せるということだ.(Hyには目に見えている構造をPythonの表現に変換する余計な工程が含まれている.Common LispやEmacs Lispのようなより"純粋な"Lispではコード上で見えているデータ構造と実行されるデータ構造は文字通りもっと近いものだ.)

もうひとつはマクロだ.プログラムの構造がシンプルなデータ構造なら,コードを書くコードを書くことは実に簡単だし,全く新しい言語仕様も非常に素早く実装できる.Hyの登場する以前はPythonプログラマー達にとってはほとんど不可能なことだったが,今や君たちもマクロの無敵のパワーを扱えるのだ.

HyはLisp風味のPython

HyはPythonの抽象構文木に変換されるのでPythonの使い慣れた全てのパワーをそのまま扱える.HyではPythonの全てのデータ構造と標準ライブラリに完全にアクセスできる.Hyのインタプリターで実験してみよう.

=> [1 2 3]
[1, 2, 3]
=> {"dog" "bark"
... "cat" "meow"}
{'dog': 'bark', 'cat': 'meow'}
=> (, 1 2 3)
(1, 2, 3)
=> #{3 1 2}
{1, 2, 3}
=> 1/2
Fraction(1, 2)

最後二行の注意:HyはClojureのような分数構文を持っている.

以下のようにHyを起動してみよう(エイリアス使えば便利かもね).

$ --repl-output-fn=hy.contrib.hy-repr.hy-repr

Pythonのreprではなくhy-repr-fnを使用して変数を表示するようになる.Pythonの文法ではなくHyの文法で値を確認できるようになる.

=> [1 2 3]
[1 2 3]
=> {"dog" "bark"
... "cat" "meow"}
{"dog" "bark" "cat" "meow"}

もし君が他のLispに慣れてるならHyがCommon Lisp式のクォートをサポートしていることに興味が湧くかもしれない.

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

組み込み型の素晴らしいメソッドにもアクセスできる.

=> (.strip " fooooo   ")
"fooooo"

これはなんだろうか?そう,これは以下と同じだ.

"fooooo   ".strip()

ご名答!Hyはドット表記を使えるLispなのだ.この文字列(This string)を変数に割り当てておけば以下のようなこともできる.

(setv this-string "fooooo   ")
(this-string.strip)

条件分岐はどうだろうか.

(if (try-some-thing)
  (print "this is if true")
  (print "this is if false"))

ifの最初の引数は条件で二番目は条件が真だった時の実行内容だ.そして三番目(なくても良い)は偽だった時の実行内容(つまりelse)だ.もっと複雑な条件分岐が必要になったとき,Hyにはelifが無いことに気づくだろう.代わりにcondと呼ばれるものをつかうこととなる.Pythonではこのようにするだろう.

somevar = 33
if somevar > 50:
    print "That variable is too big!"
elif somevar < 10:
    print "That variable is too small!"
else:
    print "That variable is jussssst right!"

Hyではこうだ.

(setv somevar 33)
(cond
 [(> somevar 50)
  (print "That variable is too big!")]
 [(< somevar 10)
  (print "That variable is too small!")]
 [True
  (print "That variable is jussssst right!")])

condは真偽を判断して条件付きで実行され真となった文へと切り替える.またelseTrueを条件として最後に書く.なぜならTrueは常に真だからだ.ここまで到達すれば常に実行されるのだ.

以下のようなコードがあるとしよう.

(if some-condition
  (body-if-true)
  (body-if-false))

ちょっと待った!もし真偽のどちらかで複数のことを実行したかったらどうするんだ?

こうする.

(if (try-some-thing)
  (do
    (print "this is if true")
    (print "and why not, let's keep talking about how true it is!"))
  (print "this one's still simply just false"))

複文のラップにはdoを使えばいい.もし他のLispに慣れているならprognと一緒だと考えれば良い.

コメントはセミコロンで始める.

(print "this will run")
; (print "but this will not")
(+ 1 2 3)  ; we'll execute the addition, but not this comment!

シバン (#!) もサポートされている.

#! /usr/bin/env hy
(print "Make me executable, and run me!")

ループは難しくないけど特殊な構造を持っている.Pythonだとこうだ.

for i in range(10):
    print "'i' is now at " + str(i)

Hyだとこう.

(for [i (range 10)]
  (print (+ "'i' is now at " (str i))))

Pythonの多様なライブラリをインポートして使うこともできる.たとえばこうだ.

(import os)

(if (os.path.isdir "/tmp/somedir")
  (os.mkdir "/tmp/somedir/anotherdir")
  (print "Hey, that path isn't there!"))

Pythonのコンテキストマネージャ (with構文)はこのように使う.

(with [f (open "/tmp/data.in")]
  (print (.read f)))

Pythoで書くとこうだ.

with open("/tmp/data.in") as f:
    print f.read()

もちろんリスト内包もあるぜ!Pythonだとこうだ.

odds_squared = [
  pow(num, 2)
  for num in range(100)
  if num % 2 == 1]

Hyだとこのようにする.

(setv odds-squared
  (list-comp
    (pow num 2)
    (num (range 100))
    (= (% num 2) 1)))
; 恥ずかしながらClojureのページから例を拝借しよう:
; チェス盤の各マスをリストアップしよう:

(list-comp
  (, x y)
  (x (range 8)
   y "ABCDEFGH"))

; [(0, 'A'), (0, 'B'), (0, 'C'), (0, 'D'), (0, 'E'), (0, 'F'), (0, 'G'), (0, 'H'),
;  (1, 'A'), (1, 'B'), (1, 'C'), (1, 'D'), (1, 'E'), (1, 'F'), (1, 'G'), (1, 'H'),
;  (2, 'A'), (2, 'B'), (2, 'C'), (2, 'D'), (2, 'E'), (2, 'F'), (2, 'G'), (2, 'H'),
;  (3, 'A'), (3, 'B'), (3, 'C'), (3, 'D'), (3, 'E'), (3, 'F'), (3, 'G'), (3, 'H'),
;  (4, 'A'), (4, 'B'), (4, 'C'), (4, 'D'), (4, 'E'), (4, 'F'), (4, 'G'), (4, 'H'),
;  (5, 'A'), (5, 'B'), (5, 'C'), (5, 'D'), (5, 'E'), (5, 'F'), (5, 'G'), (5, 'H'),
;  (6, 'A'), (6, 'B'), (6, 'C'), (6, 'D'), (6, 'E'), (6, 'F'), (6, 'G'), (6, 'H'),
;  (7, 'A'), (7, 'B'), (7, 'C'), (7, 'D'), (7, 'E'), (7, 'F'), (7, 'G'), (7, 'H')]

Pythonは多彩な引数やキーワード引数をサポートしている. Pythonではこんなのを目にするだろう.

>>> def optional_arg(pos1, pos2, keyword1=None, keyword2=42):
...   return [pos1, pos2, keyword1, keyword2]
...
>>> optional_arg(1, 2)
[1, 2, None, 42]
>>> optional_arg(1, 2, 3, 4)
[1, 2, 3, 4]
>>> optional_arg(keyword1=1, pos2=2, pos1=3, keyword2=4)
[3, 2, 1, 4]

Hyで同じことをするとこうだ.

=> (defn optional-arg [pos1 pos2 &optional keyword1 [keyword2 42]]
...  [pos1 pos2 keyword1 keyword2])
=> (optional-arg 1 2)
[1 2 None 42]
=> (optional-arg 1 2 3 4)
[1 2 3 4]

Hyのバージョンが0.10.1以上なら(例えばgit master)新しいキーワード引数の文法も使える.

=> (optional-arg :keyword1 1
...              :pos2 2
...              :pos1 3
...              :keyword2 4)
[3, 2, 1, 4]

そのほかだとapplyはどのバージョンでも使える.でもapplyってなんだ?
Pythonだと*args**kwargsで引数を渡すのに慣れているだろうか?

>>> args = [1 2]
>>> kwargs = {"keyword2": 3
...           "keyword1": 4}
>>> optional_arg(*args, **kwargs)

Hyではapplyで実現している.

=> (setv args [1 2])
=> (setv kwargs {"keyword2" 3
...              "keyword1" 4})
=> (apply optional-arg args kwargs)
[1, 2, 4, 3]

以下のようにすれば辞書スタイルのキーワード引数も使える.

(defn another-style [&key {"key1" "val1" "key2" "val2"}]
  [key1 key2])

ここでの違いは辞書なので引数を順序で特定できないということだ.

Hyは*args**kwargsもサポートしている.Pythonだとこうだ.

def some_func(foo, bar, *args, **kwargs):
    import pprint
    pprint.pprint((foo, bar, args, kwargs))

Hyだとこうだ.

(defn some-func [foo bar &rest args &kwargs kwargs]
  (import pprint)
  (pprint.pprint (, foo bar args kwargs)))

最後に,もちろんクラスも必要だ!Pythonのクラスはこんな感じだ.

class FooBar(object):
    """
    Yet Another Example Class
    """
    def __init__(self, x):
        self.x = x

    def get_x(self):
        """
        Return our copy of x
        """
        return self.x

これは以下のように使う.

bar = FooBar(1)
print bar.get_x()

Hyではこうだ.

(defclass FooBar [object]
  "Yet Another Example Class"

  (defn --init-- [self x]
    (setv self.x x))

  (defn get-x [self]
    "Return our copy of x"
    self.x))

以下のように使う.

(setv bar (FooBar 1))
(print (bar.get-x))

もしくは先行ドット構文も使える.

(print (.get-x (FooBar 1)))

クラスレベルの属性も使える.Pythonではこうだ.

class Customer(models.Model):
    name = models.CharField(max_length=255)
    address = models.TextField()
    notes = models.TextField()

Hyだとこうだ.

(defclass Customer [models.Model]
  [name (models.CharField :max-length 255})
   address (models.TextField)
   notes (models.TextField)])

マクロ

Hyのマジでパワフルな特徴のひとつはマクロだ.マクロとはコード(あるいはデータ)を生成するための小さな関数だ.Hyで書かれたプログラムが実行されると,まずマクロが実行され,その出力がコードを置き換える.その後,普通に実行される.ごく簡単な例を示そう.

=> (defmacro hello [person]
...  `(print "Hello there," ~person))
=> (hello "Tuukka")
Hello there, Tuukka

ここで注意しなきゃいけないのはhelloマクロはスクリーンに何も表示しない点だ.代わりにコードが生成され,実行され,画面に出力されるのだ.このマクロは以下のようなコード片を生成する(パラメータとして"Tuukka"を使用している場合だ).

(print "Hello there," Tuukka)

マクロを使えばコードを操作できる.

=> (defmacro rev [code]
...  (setv op (last code) params (list (butlast code)))
...  `(~op ~@params))
=> (rev (1 2 3 +))
6

このマクロで生成されたコードはいくつかの要素を切り替えただけであり,プログラムが実行されるときには,実際には以下のようになる.

(+ 1 2 3)

ひとつしかパラメータのないマクロを括弧なしで呼べればよい時もあるだろう.シャープマクロを使えば実現可能だ.シャープマクロの名前は一文字でなければならないがHyはUnicode文字列を上手に扱っているので文字をすぐさま使い果たすことはない.

=> (defsharp  [code]
...  (setv op (last code) params (list (butlast code)))
...  `(~op ~@params))
=> #(1 2 3 +)
6

マクロはHyを拡張したりHy上で自分の言語を書きたい時に便利だ.Hyの多くの機能はマクロだ.例えばwhencond->などがそうだ.

別のモジュールで定義されたマクロを使うにはどうすれば良いだろうか?importではダメだ.importは実行時にPythonのimport文に翻訳さるが,マクロはHyからPythonに翻訳されるコンパイル時に展開されるからだ.代わりにrequireを使う.これはモジュールをインポートし,コンパイル時にマクロを有効にするものだ.requireimportと同じ文法で使う.

=> (require tutorial.macros)
=> (tutorial.macros.rev (1 2 3 +))
6

Hy <-> Python の相互運用

PythonからHyを使う

PythonからもHyのモジュールを使えるぜ!

以下のコードをgreetings.hyとして保存してみてくれ.

greeting.hy
(defn greet [name] (print "hello from hy," name))

Hyを先にインポートしておけば直接Pythonから利用可能だ.

import hy
import greetings

greetings.greet("Foo")

HyからPythonを使う

HyからもPythonのモジュールを使えるぜ!

以下のコードをgreetings.pyとして保存してみてくれ.

greeting.py
def greet(name):
    print("hello, %s" % (name))

以下のようにHyから使える(importを参照).

(import greetings)
(.greet greetings "foo")

さらなる情報はHy <-> Python interopにある.

プロのTips!

Hyはいわゆる「スレッドマクロ」機能を持っている.これはClojureのマジでイカした機能だ.「スレッドマクロ」(->と書く)は深いネスト避けるために使う.スレッドマクロは各式を次の式の最初の引数に渡す.典型的な例を見てみよう.

(loop (print (eval (read))))

代わりにこのように書ける.

(-> (read) (eval) (print) (loop))

python-shを使えばスレッドマクロでパイプみたいなことがができるのだ!

=> (import [sh [cat grep wc]])
=> (-> (cat "/usr/share/dict/words") (grep "-E" "^hy") (wc "-l"))
210

もちろんこれは以下のように展開される.

(wc (grep (cat "/usr/share/dict/words") "-E" "^hy") "-l")

ずっと読みやすいだろう?スレッドマクロ使おうぜ!

27
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
19