Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

AtCoder Beginner Contest 128にCommon Lispで参加した

競技プログラミングに初参加しました.実装言語はもちろんCommon Lispです.

残念なことにコンテスト開始からしばらくたってから気付いたので,B問までしか完成させられなかった.C問で頭をひねっていたら終了.

成績はこちら.

atcoder.jp

Common Lispはそれほど実行速度が速いわけではなく(AtCoderの実行環境がどうなっているのか知らないが,なんとPerlよりもPythonよりも遅い),平均100msもかかってしまうことが多い. おそらく処理系のブートストラップかコンパイルに時間を取られているのだと思う.Cやgoは実行前にコンパイルしているのか,かなり高速に動作する. おそらく直接ソースコードを処理系に渡していて,その時間も込みで計上されているのかもしれない.できればプリコンパイルして測定するようにしてほしい.

というわけで「Common Lispは遅いクソ言語」と言われたら悲しいのでいろいろチューニングしようとしてみたけれど,なんとチューン前の方がメモリ消費も実行時間もマシという悲しい結果に. ヤンナルネ・・・

チューン前のB解答

(defun f (s)
  (let* ((n (read s))
         (ps (loop for i from 1 to n
                   for city = (read s)
                   for point = (read s)
                   collecting (list i city point)))
         (sorted1 (sort ps #'(lambda (p pp) (> (third p) (third pp)))))
         (sorted2 (sort sorted1 #'(lambda (p pp) (string< (second p) (second pp))))))
    (loop for p in sorted2
          do (format t "~A~%" (first p)))))
(f t)

そしてチューン後

(defstruct p (i 0 :type integer) (city nil :type symbol) (point 0 :type integer))
(defparameter *initial* (make-p))
(defun f ()
  (declare (optimize (speed 3) (safety 0)))
  (let* ((n (read))
         (arr (make-array `(,n) :element-type 'p :initial-element *initial* :adjustable nil))
         (_ (loop #:for i #:from 1 to n
                  #:for city symbol = (read)
                  #:for point integer = (read)
                  #:do (setf (svref arr (1- i))
                             (make-p :i i :city city :point point))))
         (sorted1 (sort arr #'> :key #'p-point))
         (sorted2 (stable-sort sorted1 #'string< :key #'p-city)))
    (loop #:for p #:across sorted2
          #:do (progn (princ (p-i p)) (terpri)))))

defstructが邪魔だったのだろうか.なにもわからない

追記(2019-05-27T01:24+09:00)

atcoder.jp

ルールを見たところ,コンパイルに

sbcl --eval '(write-line "Hello, world!")' --non-interactive > /dev/null

実行に

sbcl --script Main.lisp

が使われるようだ.コンパイルフェーズでコンパイルしてないじゃん.そら遅いわ.他の言語ではちゃんとコンパイルしてるのになんでCommon Lispだけこんな扱いなんだ・・・(何も知らない人向け豆知識: Common Lispの大抵の処理系,もちろんAtCoderで使われるSBCLは事前にコンパイルできます!)

ついにanyenvでCommon Lispが!Roswellをanyenvでインストールできるようにするラッパーを書いた

書いた.

github.com

anyenvというのはおなじみナントカenv系のツール(xxenvと呼ぶことにする)をインストールすることができるツールで,rbenvとかplenvとかをインストールすることができるのだが,なんとCommon lispの処理系であるRoswellをインストールできないではないか!!これはいけない.roswellをインストールできるようにせねばならない.

dev.classmethod.jp

しかしながらanyenvはインストールするxxenv系ツールに一定の慣習を要求しており,例えばXXENV_ROOTといった環境変数を渡してインストール先を教えたり,xxenv init -といった呼び出しを行ってxxenvの初期化を行おうとしたり,bin/以下にxxenvといった実行ファイルがあることを期待するなど,既存のxxenv系ツールの挙動や設計にかなり依存している.しかしRoswell自体はxxenv系の慣習通りに動くわけではないので,その仲介を行う必要がある.例えば何もしないとRoswellはインストール先ではなく~/.roswell配下に様々な処理系をインストールしようとするし,roswell initというコマンドは存在しない.代わりにrosコマンドがあるが,ros initは全く別の動作をするコマンドだ.したがってrosコマンドの代わりにanyenvからの呼び出しを受けて,種々の下準備を行ってrosを呼び出すためのラッパーを今回用意したのである.

このラッパーが有効であれば,anyenv install roswellでroswellがインストールされ,パスが通されるし,次回ログイン時にroswellが要求する初回起動処理が行われ,~/.anyenv/env/roswell/以下に種々のライブラリなどが配置されるようになる. またアンインストールもanyenv uninstall roswellで行え,ほぼ環境を汚さない.

今のところanyenvの提供元にこのラッパーを呼び出してくれるようにP-Rを出しているところなので,そのうち使えるようになると思う.

github.com

(ご意見募集)Common LispのASDF3のここが気に入らない・難しい

最近感じたことを列挙したお気持ちエントリです.とくに結論もオチもないです.困っているという意識だけ共有して回りの反応を見たいというエントリなので,コメントははてブなどいただけると嬉しいです.

ASDF難しい

  • エラーメッセージがけっこうわかりにくくて初見殺しなところがある

modules難しい

  • 階層をもったmodulesなどを組み合わせるとうまくコンパイルできない
    • mod1以下のコンポーネントがmod1と並列な別のコンポーネントに依存できない

Package-inferred-system難しい

  • package-inferred-systemがうまく動かない
    • マニュアルも1ページペラッと書いてあるだけで,参考にあまりならない
    • システムを見付けられないという形で失敗することが多い
    • 有名なソフトウェアがpackage-inferred-systemに対応していると参考にできそうだが,そういうのが知る限りない
    • 今時のLL言語みたいに,packageとpathが合致している挙動がやりたいだけなのに・・・
      • ふだんはperlやってるので,今時ビルド順序を指定とかしたくない
        • ビルド順序手で指定したほうがいいほどの難しいことしない

処理系でなんか動作がぶれる

  • 処理系によってなんか(特に?キーワードまわりの)挙動が違うようなきがする
    • システムをキーワードで指定して,こっちの処理系だとコンパイル通るけど,別の処理系だとコンポーネントを発見できない,といったことがある(?)
    • なんでもkeywordにすると良い??

マニュアルが難しい

  • ASDFのマニュアルが難しい
  • 結局どうしたらええねんということがある
  • ユースケースがほしい
  • そもそもASDFがかなり柔軟すぎるので困る
  • ASDFにもバージョン(マイナー)があって,バージョン上げたら直る,みたいなことがある
    • 面倒臭い!!
  • 日本語の定番ドキュメントみたいなのがあると最高だが,自分で作るほどASDFのことがわかってない
    • 助けてくれ!!

結語

みなさんはASDFとどうやってうまく付き合っていますか??

Dockerではうまく処理系が切り変わらない?Common Lispの環境構築するときは処理系の統一に注意しよう

TL;DR

  • Roswellで処理系を指定しても,Roswellでインストールしたコマンド類はそのコマンドをビルドしたときの処理系で実行される?
  • シェルからコマンドを呼ばずにros runで実行するなどの手立てがある

Dockerで再現したがシェルで実験すると再現しないので困っている.

FROM ubuntu:16.04

ENV libs 'automake libcurl4-gnutls-dev make gcc curl bzip2 locales libev-dev tidy git'
RUN apt-get update \
  && apt-get install -y ${libs}

ENV roswell_archive_url 'https://github.com/roswell/roswell/archive/release.tar.gz'
RUN echo 'install roswell' \
  && curl -SL ${roswell_archive_url} \
  | tar -xzC /tmp/ \
  && cd /tmp/roswell-release \
  && sh bootstrap \
  && ./configure \
  && make \
  && make install \
  && rm -rf /tmp/roswell-release

# locale setting
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

RUN ros setup
RUN ros install ccl-bin/1.11
RUN ros use ccl-bin/1.11

RUN ros install fukamachi/qlot
RUN ros install clack
ENV PATH /root/.roswell/bin:/usr/local/bin:$PATH

# Assuming whole application directory is mounted as /app
WORKDIR /app/
CMD ["bash"]

こういったDockerfileで環境を作る.この時点でqlotがインストールされるはず.

$ docker build -t my-roswell .
$ docker run -it --rm my-roswell bash
# echo '(format t "~S~%" *features*)' >> ~/.roswell/init.lisp
# echo 'ql alexandria :latest' >> qlfile
# qlot install
features: (:ROS.INIT :X86-64 :64-BIT :64-BIT-REGISTERS :ALIEN-CALLBACKS
           :ANSI-CL :C-STACK-IS-CONTROL-STACK :CALL-SYMBOL :COMMON-LISP
           :COMPACT-INSTANCE-HEADER :COMPARE-AND-SWAP-VOPS :COMPLEX-FLOAT-VOPS
           :CYCLE-COUNTER :ELF :FLOAT-EQL-VOPS :FP-AND-PC-STANDARD-SAVE
           :GCC-TLS :GENCGC :IEEE-FLOATING-POINT :IMMOBILE-CODE :IMMOBILE-SPACE
           :INTEGER-EQL-VOP :LARGEFILE :LINKAGE-TABLE :LINUX :LITTLE-ENDIAN
           :OS-PROVIDES-DLADDR :OS-PROVIDES-DLOPEN :OS-PROVIDES-GETPROTOBY-R
           :OS-PROVIDES-POLL :OS-PROVIDES-PUTWC :OS-PROVIDES-SUSECONDS-T
           :PACKAGE-LOCAL-NICKNAMES :RELOCATABLE-HEAP :SB-DOC :SB-EVAL
           :SB-FUTEX :SB-LDB :SB-PACKAGE-LOCKS :SB-SIMD-PACK
           :SB-SOURCE-LOCATIONS :SB-THREAD :SB-UNICODE :SBCL
           :STACK-ALLOCATABLE-CLOSURES :STACK-ALLOCATABLE-FIXED-OBJECTS
           :STACK-ALLOCATABLE-LISTS :STACK-ALLOCATABLE-VECTORS
           :STACK-GROWS-DOWNWARD-NOT-UPWARD :UNDEFINED-FUN-RESTARTS :UNIX
           :UNWIND-TO-FRAME-AND-CALL-VOP)
...

SBCLが動いてるやんか!!CCLが動いてほしいの!!

その場でできる対策としては,以下のようにqlotコマンドではなく直接qlotをロードして関数を呼び出すこと.

ros run -e "(progn (ql:quickload :qlot) (qlot:install))" -q

シェルで実験

$ ros install sbcl/1.5.0
$ ros use sbcl/1.5.0
$ ros install qlot
$ echo '(format t "~S~%" *features*)' >> ~/.roswell/init.lisp
$ echo 'ql alexandria :latest' >> qlfile
$ qlot install
(:ROS.INIT :X86-64 :64-BIT :64-BIT-REGISTERS :ALIEN-CALLBACKS :ANSI-CL :AVX2
 :C-STACK-IS-CONTROL-STACK :CALL-SYMBOL :COMMON-LISP :COMPACT-INSTANCE-HEADER
 :COMPARE-AND-SWAP-VOPS :CYCLE-COUNTER :ELF :FP-AND-PC-STANDARD-SAVE :GENCGC
 :IEEE-FLOATING-POINT :IMMOBILE-CODE :IMMOBILE-SPACE :INTEGER-EQL-VOP
 :LARGEFILE :LINKAGE-TABLE :LINUX :LITTLE-ENDIAN :OS-PROVIDES-BLKSIZE-T
 :OS-PROVIDES-DLADDR :OS-PROVIDES-DLOPEN :OS-PROVIDES-GETPROTOBY-R
 :OS-PROVIDES-POLL :OS-PROVIDES-PUTWC :OS-PROVIDES-SUSECONDS-T
 :PACKAGE-LOCAL-NICKNAMES :RELOCATABLE-HEAP :SB-CORE-COMPRESSION :SB-DOC
 :SB-EVAL :SB-FUTEX :SB-LDB :SB-PACKAGE-LOCKS :SB-SIMD-PACK :SB-SIMD-PACK-256
 :SB-SOURCE-LOCATIONS :SB-THREAD :SB-UNICODE :SBCL :STACK-ALLOCATABLE-CLOSURES
 :STACK-ALLOCATABLE-FIXED-OBJECTS :STACK-ALLOCATABLE-LISTS
 :STACK-ALLOCATABLE-VECTORS :STACK-GROWS-DOWNWARD-NOT-UPWARD
 :UNDEFINED-FUN-RESTARTS :UNIX :UNWIND-TO-FRAME-AND-CALL-VOP)
...

$ ros install ccl-bin/1.11
$ ros use ccl-bin/1.11
$ qlot install
(:ROS.INIT :PRIMARY-CLASSES :COMMON-LISP :OPENMCL :CCL :CCL-1.2 :CCL-1.3 :CCL-1.4 :CCL-1.5 :CCL-1.6 :CCL-1.7 :CCL-1.8 :CCL-1.9 :CCL-1.10 :CCL-1.11 :CLOZURE :CLOZURE-COMMON-LISP :ANSI-CL :UNIX :OPENMCL-UNICODE-STRINGS :IPV6 :OPENMCL-NATIVE-THREADS :OPENMCL-PARTIAL-MOP :MCL-COMMON-MOP-SUBSET :OPENMCL-MOP-2 :OPENMCL-PRIVATE-HASH-TABLES :STATIC-CONSES-SHOULD-WORK-WITH-EGC-IN-CCL :X86-64 :X86_64 :X86-TARGET :X86-HOST :X8664-TARGET :X8664-HOST :LINUX-HOST :LINUX-TARGET :LINUXX86-TARGET :LINUXX8664-TARGET :LINUXX8664-HOST :64-BIT-TARGET :64-BIT-HOST :LINUX :LITTLE-ENDIAN-TARGET :LITTLE-ENDIAN-HOST)
...

あれ〜・・・こんどはちゃんと処理系が切り替わってる・・・

どうしてこうなってしまうのかまるで分からないので,何か知見をお持ちの方がいらっしゃいましたら教えてください・・・

Common Lispで文字列をキーにするのはあまりおすすめできない

まあ慣れかもしれないが,Common Lispで文字列をキーとした辞書,つまりalistやplistを作るとあまり使い勝手が良くないので避けたいと思うようになった. なぜなら標準的なリスト操作関数では比較をeqlで行うことが多い(要出展)ので,辞書構造を操作する際に文字列がキーだと支障をきたすことが多いからだ.

ちなみにJSONライブラリであるJSOWNはキーが文字列となった状態でJSONオブジェクトを表現するので扱いにくい.これはまあJSONオブジェクトの特性上仕方がないことなのだろうが・・・

:testを用いたナイーブな回避方法

このような場合に重宝するのは:testキーワードを各種コレクション操作関数のオプションとして渡すことだ.:test #'string=とすることで文字列のキーを正しく比較することができるようになる.

ただし,コレクション操作関数がライブラリなどに組込まれて使われていて:testオプションを変更できない場合は対処不能だ.例えばパターンマッチライブラリtriviaを使った以下のようなスクリプトについて考えてみよう.

(ql:quickload :trivia)
(match '(("a" . 1) ("b" . 2))
  ((alist ("a" . x) ("b" . y))
    (cons x y)))

これはalist(("a" . 1) ("b" . 2))の中の特定の箇所をパターンマッチによって取り出そうとする処理だが,このalistのパターンマッチは内部でassocによる比較を利用しており,(相当面倒なことをしなければ)外から:testを変更することができない.したがって,このパターンマッチは失敗する.

ちなみにassocは比較にeqlを使うので,以下のような場合はパターンマッチに成功する.

(ql:quickload :trivia)
(match '((:a . 1) (:b . 2))
  ((alist (:a . x) (:b . y))
    (cons x y)))

自分がよくやる方法

ちなみに自分はmapcarを使ってalistのキーを全てキーワードに変換してしまった.キーワード名前空間を汚してしまうが,手っ取り早い解決方法だ. 文字列からキーワードを生成するには,ALEXANDRIAライブラリのmake-keywordが役に立つ.

任意のCommon Lisp(Roswell)スクリプトをAWS Lambda化するツール「Lambda-over-Lambda」の紹介

任意の.rosファイルを変換して,AWS Lambda Custom Runtimeが受け取る.zipに変換するツールができあがったので紹介と使い方のメモを兼ねてここに書いておく. 実際に動作するランタイムは,IPv6まわりのトラブルの影響でClozureCLを使っているが,今後余裕ができればSBCLでも動かせるようにしたい.

github.com

使い方

まずLambda化したいroswellスクリプトを用意する.任意のRoswellスクリプトを変換できると言ったもののmain関数は以下のフォーマットに従う必要があるので,適宜修正する*1

  • 第1引数としてhandlerを,第2引数としてlambda eventを受け取る
    • eventはJSOWNのオブジェクト
    • truetに変換される
    • false:fに変換される
    • null:nullに変換される
    • []nilに変換される
  • JSOWNのオブジェクトを返す必要がある

ちなみにJSOWNとはCommon Lisp用のJSONシリアライザ・デシリアライザであり,S式とJSONとを相互変換できる.

上記の要件を満たすmain関数は例えば次のような形になるだろう.

(defun main (handler event)
  `(:obj ("result" . "ok")
         ("handler" . ,handler)
         ("event" . ,event)))

このmain関数は,ハンドラとイベントを含んだ簡易なオブジェクトを生成し,それを返す.JSOWNオブジェクトを返すので,返り値は:objで始まる連想リストになっている.

準備は整った.lambda-over-lambda.rosを使って先程のスクリプトを変換してみよう.同様のスクリプトがリポジトリ内のyou-should-try-convert-this-script.rosに保存されているので,ぜひ試してみてほしい.

% git clone git@github.com:windymelt/lambda-over-lambda.git
% cd lambda-over-lambda/
% ./roswell/lambda-over-lambda.ros you-should-try-convert-this-script.ros
 => out.zip

このスクリプトをCustom Runtimeとして登録する.

f:id:Windymelt:20190216223346p:plain
出力されたzipファイルを関数パッケージとしてLambdaにアップロードする

アップロードに成功したなら,テストボタンを使ってテストイベントを発行してみよう.

f:id:Windymelt:20190216223454p:plain
テスト実行に成功した

Roswell scriptがLambdaに変換された.たぶんどんなスクリプトも動くはずなので,みんなで遊んでみてほしい.ネイティブコンパイルされているのでけっこう速いぞ.

あらすじ

先日こういった記事を書いていた.

blog.3qe.us

blog.3qe.us

動かすのには成功したので,これを押し進めていたところ,今回のスクリプトが完成した.

仕組み

Lambda-over-Lambda(以下lol)はいくつかのステップでソースの.rosファイルをCustom Runtimeに変換する.

ランタイムコードの合成

lolはターゲットコードにLambda用のラップコードを合成し,必要なライブラリとあわせてzipファイルに固める.

ふつうの関数は引数を受取り結果を返すものだが,AWS Lambda Custom RuntimeではこれをHTTPリクエストの送受信で実現している. Lambdaが用意している特定のエンドポイントにアクセス(GET)するとイベントを受け取ることができ,また別の特定のエンドポイントにアクセス(POST)するとLambdaファンクションとして結果を返すことができるのだ.

Lolはイベントの取り出しと結果の返却をラップするための追加のコードを生成し,ソースと合体させてターゲット.rosファイルを生成する.((今のところ適当にtemporary.rosといった名前で生成しているが,そのうちちゃんとしたい.))ラップコードはmain関数を乗っ取り,Lambdaからイベントを受け取るためのやりとりをすませると,オリジナルのmain関数を呼び出し,得られた結果をまた変換してLambdaに送り返す.

これにより,ソースroswellスクリプトは一般的な入出力方法しか使わずにLambdaとやりとりできるようになる.現在は以下のようなI/Oをサポートしている.

  • eventをJSOWNライブラリのobjectとしてargvで受け取り,JSOWNライブラリのobjectをmainの返り値とする

またこのほかにも,以下のようなI/Oが考えられる.まだ未実装だがこれから必要に応じて実装していきたい.

  • eventの特定フィールドを標準入力として受け取り,標準出力をキャプチャしてLambdaの返り値に(JSONの1フィールドにするなどして)変換する

大変だった点

限定的なIPv6サポート

LambdaのランタイムではIPv6が正しく動作しないようで,これに起因するソケットのエラー(ソケットをオープンしようとするとシステムコールに失敗する)に見舞われ,開発が停滞してしまった.

解決策としてはIPv6(正確にはソケットライブラリであるUSOCKETのIPv6サポート)をオフにするという手段をとった.詳細は以下のエントリにまとめている.

blog.3qe.us

OpenSSLのバージョン違い

LambdaのランタイムはAmazon Linuxで動作しているようで,インストールされているOpenSSLのバージョンもこれに準じているようだ.そのため自分で適当にLispバイナリをビルドしてアップロードしても,(ビルド時の情報をもとに共有ライブラリを読み込むようなので)共有ライブラリの読み込みに失敗して死んでしまう.こういうことはスクリプト型言語ではあまり陥ることのない穴なので面喰らってしまった.

Common Lisp,とくにネイティブコンパイラを備えたSBCLとClozureCLのような処理系では,スクリプト言語のような動的な一面とコンパイル型言語のような静的な一面をあわせもった挙動になるので注意してやる必要がある.例えばグローバルスコープで(defparameterなどで)宣言したスペシャル変数は,実行時ではなくrosスクリプトのビルド時に評価されるので,当然実行時に評価されるものと思ってコーディングすると痛い目を見ることになる.ファイルが一度読み込まれ,評価され,その状態がバイナリとして産み落され,あらためてmain 関数から実行される,というイメージを持つ必要がある.

ともかくOpenSSLの問題はDockerを使ってLambdaと互換な環境でバイナリを生成するという手段で乗りきった.amazonlinux:2017.03.1.20170812というベースイメージでroswellをコンパイルし,処理系をインストールすることでなんとかAPIの互換性を保つことができた.OpenSSLうまく動かすのつらすぎる・・・

まあ動いてよかった

AWSという最新のインフラにもついていける体力がCommon Lispにもあることを示していきたい.COBOLに負けないぞ.

*1:必要が生じればさまざまなフォーマットに対応させるつもりだ

AWS LambdaでCommon Lispの任意の関数を動かせるようにした

blog.3qe.us

これでAWS Lambdaで動作するCommon Lispランタイムを実装したが,これを強化して,ハンドラを介してシステム内にある任意の関数を呼び出せるようにしたので紹介する.

f:id:Windymelt:20190207203617p:plain
ハンドラに完全修飾した関数名を指定できる

この機能もリポジトリに入れてある.

github.com

また,この機能の実装は以下を参考にした.

[http://y2q-actionman.hatenablog.com/entry/2018/12/06/AWS_Lambda%E3%81%AE_Custom_Runtime%E3%81%A8%E3%81%97%E3%81%A6_Common_Lisp%28sbcl%29%E3%82%92%E4%BD%BF%E3%81%86:title]

関数を指定する

ハンドラで関数を指定するには,完全修飾した関数名をハンドラに与える.例えばfooパッケージのhoge関数を指定するにはfoo:hogeと記述する.非公開の関数を指定するにはfoo::hogeのように指定する.

関数にはLambdaにリクエストしたbodyが引数として渡ってくる.これを処理して文字列を返すと,Lambdaのリクエストに値を返すことができる.

上掲のスクショではCL-USER:IDENTITY(引数をそのまま返す標準関数)を指定し,そのままリクエストしたbodyを返している.

f:id:Windymelt:20190207204638p:plain
渡したbodyがそのまま返る

AWS LambdaでCommon Lispを実行し,ロードされたシステムの任意の関数を呼び出せるようになった.

実装

主要な部分は以下の通り.

(defun handler->function (handler-string)
  "Returns function object designated by HANDLER-STRING, formatted in \"PACKAGE:SYMBOL\" form.
 Package name cannot be omitted."
  (let* ((split (multiple-value-list (cl-ppcre:scan-to-strings "([^:]+)::?(.+)" handler-string)))
         (pkg-name (string-upcase (elt (cadr split) 0)))
         (func-name (string-upcase (elt (cadr split) 1)))
         (pkg (find-package pkg-name)))
    (if pkg
        (find-symbol func-name (find-package pkg))
        nil)))

(defun default-handler (content)
  "Only returns message."
  (declare (ignorable content))
  "Handler not found.")

(defun get-handler (handler)
  "Returns function object from string using HANDLER->FUNCTION.
 When symbol not found or error occurred, DEFAULT-HANDLER is returned."
  (handler-case
      (or (handler->function handler)
          #'default-handler)
    (error () #'default-handler)))

(defun main-loop (handler)
  "Main loop processes lambda requests. Specify function designator with HANDLER."
  (declare (ignorable handler))
  (let ((next-endpoint
          (format nil "http://~A/2018-06-01/runtime/invocation/next" *aws-lambda-runtime-api*)))
    (loop for (body status headers . nil) = (multiple-value-list (dex:get next-endpoint))
          as request-id = (gethash "lambda-runtime-aws-request-id" headers)
          as response = (funcall (get-handler handler) body)
          as response-endpoint = (format nil "http://~A/2018-06-01/runtime/invocation/~A/response" *aws-lambda-runtime-api* request-id)
          do (dex:post response-endpoint :content response))))

今後の展望

  • status codeを関数に渡せるようにする?
  • 成功させず関数がエラーを返せるようにする?
  • 既存のシステムをLambda化するラッパーを実装する?
★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?