TIL

Today I Learned. 知ったこと、学んだことを書いていく

四則演算 - Lisp

整数と浮動小数点数がある。小数点をつけると浮動小数点数になる。

加算は+

> (+ 1 2)
3
> (+ 1 2.0)
3.0

整数と浮動小数点数を混ぜると浮動小数点数になる

減算は-

> (- 3 4)
-1

乗算は*

> (* 2 3)
6

除算は/

> (/ 3 4)
3/4

どちらも整数だと有理数を返す。すごい数学的だなと思った。有理数は分数で表せる数?

少数で返してほしいときは、引数のいずれかに小数点をつければいい。

> (/ 3 4.0)
0.75
> (/ 3.0 4)
0.75

あまりはmod

> (mod 8 3)
2

参考文献

コンスセルとconsとcarとcdr

Lispはすべてリストでできている。また、リストとリストを繋ぐ役目にコンスセルというものがある。これはすごい面白い考え方だと思った

コンスセルは2つの部屋(セル)でできている。1つ目のセル(CARと呼ばれる)にはデータなどを指し、2つ目のセル(CDRと呼ばれる)には別のコンスセルやnilというリストの終わりを示す特別なシンボルを指すことができる。
また、データそのものが入るわけではなく、データの参照を指し示すようになっている。ポインタみたいな?

Lispではコンスセルの連なりとリストは全く同じものとして扱われる。

コンスセルについてはAbout Cons Cellsがわかりやすかった

Lispのプログラムからコンスセルを扱うにはcons関数を使う。ほかにもcarcdrがある。

cons関数

> (cons 1 2)
(1 . 2)

これは、1と2をコンスセルで繋いでいますよっていうこと。リストと区別するために、.が入っている

> (cons 1 'nil)
(1)

第2引数にリストの終りを示す'nilを渡すと、通常のリストのような表示になる。(1 . 'nil)と同じ意味。Lispはできるだけ、コンスセルではなくリストとして見せてくれている!

> (cons 1 ())
(1)

Common Lispでは、空のリスト()とシンボル'nilは同じ意味となっている。そのためこのようにも書ける。
「空のリストに1を追加したら、1だけのリストになる」と考えればしっくりきた。

どんなリストもコンスセルで表せる!

> '(1 2 3)
(1 2 3)
> (cons 1 (cons 2 (cons 3 'nil)))
(1 2 3)
> (cons 1 (cons 2 (cons 3 ())))
(1 2 3)

car関数とcdr関数

コンスセルを扱うための関数であるcar(カー)とcdr(クダー)。それぞれ、コンスセルの部屋の構造と一致している。

carで1つ目のコンスセルのCARセル。cdrで1つ目のコンスセルに紐づくデータが取得できる。

> (car '(dog cat chicken))
DOG
> (cdr '(dog cat chicken))
(CAT CHICKEN)
> (cdr (cons 'b 'nil))
NIL

組み合わせることで取得したい場所を取得できる。ちょっと複雑になるけど。。。

例)2番目の要素を取得する

> (car (cdr '(dog cat chicken)))
CAT

cdrで2番目以降の要素を取得し、それに対してcarで1番目の要素(2番目)を取得する

一度に取得できる関数も用意されている

> (cadr '(dog cat chicken))
CAT
> (cadr '(dog cat chicken))
CAT
> (cddr '(dog cat chicken))
(CHICKEN)
> (caddr '(dog cat chicken))
CHICKEN
> (cdddr '(dog cat chicken))
NIL

各順番のとおりにadを書けばいいのかも。(car (cdr '(...)))だから、(cadr '(...))みたいに。

最後のコンスセルにはnilが入っていることを忘れずに!!!

cdadadrをやろうとしたら、そんなの無いよ!!!っていわれた。最大4つまでらしい。

> (cdadadr '((peas carrots tomatoes) (pork beef chicken)))

*** - EVAL: undefined function CDADADR

list関数

list関数を使うと簡単にリストが作れる。

以下の3つは同じ意味!!

> (list 'dog 'cat 'chicken)
(DOG CAT CHICKEN)
> '(dog cat chicken)
(DOG CAT CHICKEN)
> (cons 'dog (cons 'cat (cons 'chicken ())))
(DOG CAT CHICKEN)

nilを含まないデータでcdrを使うと

なんかおもしろいと思った。

> (cons 'a (cons 'b 'c))
(A B . C)
> (cdr (cons 'a (cons 'b 'c)))
(B . C)
> (cddr (cons 'a (cons 'b 'c)))
C

.は要素なのかな!?って思ってみたけど、そんなことはなかった。。。

listとクオートの快適さを実感した

複雑なリストを作るときにちょっと手こずった。

> (cons (cons 'peas (cons 'carrots (cons 'tomatoes ())))
        (cons 'pork (cons 'beef (cons 'chicken ()))))
((PEAS CARROTS TOMATOES) PORK BEEF CHICKEN)

((PEAS CARROTS TOMATOES) (PORK BEEF CHICKEN)) ってなってほしい!!

> (cons (cons 'peas (cons 'carrots (cons 'tomatoes ())))
        (cons (cons 'pork (cons 'beef (cons 'chicken ()))) ()))
((PEAS CARROTS TOMATOES) (PORK BEEF CHICKEN))

できたけど、ややこしいよ!!!!!!

これをやったあとにlistやクオートを使うとなんと楽なことか

> (list (list 'peas 'carrots 'tomatoes) (list 'pork 'beef 'chicken))
((PEAS CARROTS TOMATOES) (PORK BEEF CHICKEN))
> '((peas carrots tomatoes) (pork beef chicken))
((PEAS CARROTS TOMATOES) (PORK BEEF CHICKEN))

あぁ、楽すぎる...まじでやばい

Lispを始める

Lispになぜか興味を持ってしまい、本まで買ってしまった。

Lispの魅力は

  • Lisp自身をLispで書くのが容易
  • 問題に対応して書きやすい形に変化させて書ける
  • 拡張されることを前提にして作られている

たぶん、もっとあった気がする

Lispの種類

ANSI Common LispSchemeというのが有名らしい。最近見たのはClojureっていう言語も有名かも?

clispをインストールする

clispというCommon Lispで書かれたコンパイラを使って、書いていく

CLISP - an ANSI Common Lisp Implementation

brew install clisp
clisp --version

さぁ!Lispをはじめるぞ!!

roswellっていうのもあるらしいけど、とりあえず、最初はclispでやってみる。基礎がわかってきたらroswellでやってみよう。


インストール完了時に出てきた文字がちょっと気になった

==> clisp
Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/clisp

Emacs!?なんですと!?emacsとコマンドを打ってみたら、Emacsが起動した。終了の仕方わからなくておどおどしてた。C-x C-cで終了できた

参考文献

gzipモジュールを使った圧縮と展開 - Python

vcrpyでは、レスポンスの量が多い場合、圧縮されて(バイナリになって)カセットに格納される。どうやって圧縮されたデータを扱うのか調べてみたら標準モジュールのgzipモジュールでできそうだったため、メモしておく

圧縮にはgzip.compress()メソッド。展開にはgzip.decompress()メソッド。それぞれ、バイト配列を扱う。

文字列を圧縮して、展開して、取得してみた

>>> import gzip
>>> t_bytes = bytes('あいう123abc', 'utf-8')
>>> t_bytes
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86123abc'

>>> compressed_bytes = gzip.compress(t_bytes)    # 圧縮
>>> compressed_bytes
b'\x1f\x8b\x08\x00G::[\x02\xff{\xdc\xd8\xf4\xb8\xb1\xe5qc\x9b\xa1\x91qbR2\x00\xe1\xc7\x079\x0f\x00\x00\x00'

>>> decompressed_bytes = gzip.decompress(compressed_bytes)    # 展開
>>> decompressed_bytes
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86123abc'

>>> decompressed_bytes.decode('utf-8')
'あいう123abc'

gzip.compress()にはバイト配列(bytes)を渡さないといけないため、bytes(str, 'utf-8')でバイト配列を作成する

gzip.compress()に作成したバイト配列を渡して、圧縮する。

展開にはgzip.decompress()を使う。圧縮されているバイト配列を渡すことで、展開してくれる

展開したバイト配列をbytes.decode()で文字列に変換し、終了

これで、一応、圧縮と展開ができるようになった

参考文献

mypyとjedi-vimで補完を強くする - Vim

Pythonで型アノテーションを使いたくなって、どうせならVimでも補完できるようにしたいと思って、やってみた

Python3が有効になっているか確認

まずはVimでPython3が有効になっているか確認する
もし有効になっていない場合、ここを参考にして、有効にする

$ pip3 install -U flake8 mypy-lang jedi

neomakeを入れる

非同期で型、スタイルチェックを行うにはneomakeを使う

neomake/neomake: Asynchronous linting and make framework for Neovim/Vim

call dein#add('neomake/neomake')

...
let g:neomake_python_enabled_makers = ['python', 'flake8', 'mypy']

autocmd FileType python setlocal omnifunc=jedi#completions
if !exists('g:neocomplete#force_omni_input_patterns')
    let g:neocomplete#force_omni_input_patterns = {}
endif
let g:neocomplete#force_omni_input_patterns.python = '\%([^. \t]\.\|^\s*@\|^\s*from\s.\+import \|^\s*from \|^\s*import \)\w*'

syntasticを入れる

なんとなくsyntasticも入れた

vim-syntastic/syntastic: Syntax checking hacks for vim

call dein#add('scrooloose/syntastic')

...

let g:syntastic_check_on_open = 1
let g:syntastic_python_checkers = ['flake8', 'pep257', 'mypy']
let g:syntastic_python_flake8_args = '--max-line-length=120'

入れるときにエラーとかのメモ

以下の記述を書かないとエラーになった

if !exists('g:neocomplete#force_omni_input_patterns')
    let g:neocomplete#force_omni_input_patterns = {}
endif

こんなエラー

E121: Undefined variable: g:neocomplete#force_omni_input_patterns

あと、無効にしていたから、動作しなくて焦った

let g:jedi#completions_enabled = 0

jedi#completetions_enabledは、jedi-vimの補完を有効にするかどうかってことらしい。deoplete-jediを使うときにはこれを無効(0)にするらしい

参考文献

GETリクエストのパラメータの空白はそのままでいい requests - Python

requests.get()でGETリクエストを送信するときのパラメータに空白が入るときはどうすればいいのかわからなかったが、そのまま渡していいことがわかった

import requests

params = {'aaa': '111 あああ',
          'bbb': '222 いいい'}

res = requests.get('http://google.com', params=params)

リクエストされるURLは'http://google.com/?aaa=111+%E3%81%82%E3%81%82%E3%81%82&bbb=222+%E3%81%84%E3%81%84%E3%81%84'となる


requestsのソースを追っていったらなんかわかった気がするからメモしておく

パラメータがエンコードされるまでのスタックトレース(下から上に呼び出している)

urlencode() urllib.parse.py:906
_encode_params() models.py:105
prepare_url() [PreparedRequest] models.py:423
prepare() [PreparedRequest] models.py:305
prepare_request() [Session] sessions.py:437
request() [Session] sessions.py:494
request() api.py:58
get() api.py:72

urllib.parse.urlencode()が最終的に呼ばれていたため、' 'が'+'になっているということ!!あまり気にしなくても良かったのかも...

参考文献

github.com

requests.apiモジュール - Python

https://github.com/requests/requests/blob/00fd4c8eb4ac0fd7b8f8d76bbf15ab06351c052c/requests/api.py#L16-L58 から重要なところを引っ張ってきた

def request(method, url, **kwargs):
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)

リクエストを送る前に毎回Sessionを生成しているということ。また、withを使ってるからそのセッションは閉じられている。セッションは必ず閉じないといけない。

使用例

セッションを使うときはrequests.Sessionを使い、セッションを使わないときはrequests.apiを使うみたいな?

import requests


class Requester(object):
    _session = None

    def __init__(self, requests_session=True):
        if requests_session:
            self._session = requests.Session()
        else:
            from requests import api
            self._session = api

    def internal_call(self):
        r = self._session.request('GET', 'http://google.com')
        print(r.status_code)

if __name__ == '__main__':
    requester = Requester(False)
    requester.internal_call()
    requester = Requester()
    requester.internal_call()

実行結果

200
200

参考文献