Lambda で Scheme を実行する

Lambda で Scheme を実行する:

AWS Lambda Custom Runtimes芸人 Advent Calendar 2018 の 7日目です。

AWS Lambda で Custom Runtime がサポート され、任意の言語を Lambda 上で実行できるようになりました。

λλλλλλλλ… Lambda といったらやっぱり Lisp ですよね。

というわけで、Lisp方言の1つである Scheme の実装である Racket を Lambda で動かしてみました。

Custom Runtime の仕組みは こちら


Runtime を実装する

ソースコードは GitHub にあげてあります。

Runtime 本体の実装はこんな感じです。

runtime.rkt
(require json) 
(require net/http-client) 
 
;;; 
;;; 環境変数から必要な情報を取得する. 
;;; 
;;; _HANDLER 変数からファイル名とHandler関数名を取り出す. 
;;; 
(define-values (filename handler-name) 
  (let* ([_handler (getenv "_HANDLER")] 
         [hs (string-split _handler ":")]) 
    (values (string-join (drop-right hs 1) ":") 
            (string->symbol (last hs))))) 
(define lambda_task_root (getenv "LAMBDA_TASK_ROOT")) 
(define-values (api host port) 
  (let* ([_api (getenv "AWS_LAMBDA_RUNTIME_API")] 
         [ps (string-split _api ":")]) 
    (values _api (car ps) (string->number (cadr ps))))) 
 
;;; 
;;; Handler関数を取り出す 
;;; 
(define handler 
  (let ([ns (make-base-namespace)]) 
    (parameterize ([current-namespace ns]) 
      (namespace-require 'racket ns) 
      (load filename)) 
    (namespace-variable-value handler-name #t #f ns))) 
 
;;; 
;;; Handler 関数を呼び出す. 
;;; 
;;; 1. Custom Runtime APIからEvent情報を取得する 
;;; 2. Handler関数にEventオブジェクトを渡して呼び出す 
;;; 3. 結果を Custom Runtime APIに渡す 
;;; 4. 以上を繰り返し 
;;; 
(let loop () 
  (define-values (status _headers body) 
    (http-sendrecv 
      host 
      (string-append "http://" api "/2018-06-01/runtime/invocation/next") 
      #:port port)) 
 
  (define headers 
    (make-hash 
      (map 
        (lambda (v) 
          (let* ([vs (string-split (bytes->string/utf-8 v) ":" #:trim? #f)] 
                 [key (string-trim (car vs))] 
                 [val (string-trim (string-join (cdr vs) ":"))]) 
            (cons key val))) 
        _headers))) 
  (define requet-id (hash-ref headers "Lambda-Runtime-Aws-Request-Id")) 
 
  (define event (read-json body)) 
  (define context (hasheq)) 
 
  (define response (handler event context)) 
 
  (http-sendrecv 
    host 
    (string-append "http://" api "/2018-06-01/runtime/invocation/" requet-id "/response") 
    #:port port 
    #:data 
      (cond 
        [(or (hash? response) (list? response)) 
          (with-output-to-string (lambda () (write-json response)))] 
 
        [(void? response) ""] 
 
        [else (~a response)]) 
    #:method "POST") 
 
  (loop)) 
Runtimeスクリプトを呼び出すための bootstrap スクリプトも用意します。

bootstrap.sh
#!/bin/sh 
 
runtime_dir=$(dirname $0) 
cd ${LAMBDA_TASK_ROOT} 
${runtime_dir}/racket/bin/racket -f ${runtime_dir}/runtime.rkt 
runtime.rkt, bootstrap.sh と Racket 処理系をまとめてzipパッケージを作成します。

mkdir -p build 
 
# dowload Racket 
curl -o /tmp/racket.sh https://mirror.racket-lang.org/installers/7.1/racket-minimal-7.1-x86_64-linux.sh 
echo yes | sh /tmp/racket.sh --in-place --dest build/racket 
 
# add runtime.rkt and bootstrap 
(cd build && zip ../runtime.zip -q -r .) 
chmod +x bootstrap 
zip runtime.zip runtime.rkt bootstrap 
この zip パッケージを Lambda Layer としてデプロイします。

AWS CLI やコンソールからもできるのですが、ここでは AWS SAM テンプレートから作ってみます。

template.yml
--- 
AWSTemplateFormatVersion: '2010-09-09' 
Transform: 'AWS::Serverless-2016-10-31' 
Resources: 
  SampleFunction: 
    Type: 'AWS::Serverless::LayerVersion' 
    Properties: 
      LayerName: scheme-runtime 
      Description: scheme (racket) runtime 
      ContentUri: runtime.zip 
      LicenseInfo: 'Available under the LGPL license.' 
aws cloudformation package \ 
  --template-file template.yml \ 
  --output-template-file packaged-template.yml \ 
  --s3-bucket ${S3_BUCKET} 
aws cloudformation deploy \ 
  --template-file packaged-template.yml \ 
  --stack-name ${STACK_NAME} 
以上で、Lambda から Scheme スクリプトを実行する準備ができました。


Lambda 関数を用意する

Scheme といったらやっぱり SICP ですよね。

SICP の練習問題を Lambda で実行してみます。

https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5

Exercise 3.69.

Write a procedure triples that takes three infinite streams, S, T, and U, and

produces the stream of triples (Si,Tj,Uk) such that i < j < k. Use triples to

generate the stream of all Pythagorean triples of positive integers, i.e.,

the triples (i,j,k) such that i < j and i2 + j2 = k2.
遅延ストリームを使ってピタゴラス数を探そうという問題です。

コードを書きます。

function.rkt
(require json) 
 
(define integers 
  (stream-cons 0 (stream-map (lambda (x) (+ x 1)) integers))) 
(define integers+ (stream-rest integers)) 
 
(define (interleave s1 s2) 
  (if (stream-empty? s1) 
      s2 
      (stream-cons (stream-first s1) 
                   (interleave s2 (stream-rest s1))))) 
 
(define (pairs s t) 
  (stream-cons 
   (list (stream-first s) (stream-first t)) 
   (interleave 
    (stream-map (lambda (x) (list (stream-first s) x)) 
                (stream-rest t)) 
    (pairs (stream-rest s) (stream-rest t))))) 
 
(define (triples s t u) 
  (let ((ss (stream-map (lambda (x) (cons (stream-first s) x)) (pairs t u)))) 
    (stream-cons 
      (stream-first ss) 
      (interleave 
        (stream-rest ss) 
        (triples (stream-rest s) (stream-rest t) (stream-rest u)))))) 
 
(define pythagorean-triples 
  (stream-filter 
    (lambda (x) (= (+ (expt (car x) 2) 
                      (expt (cadr x) 2)) 
                   (expt (caddr x) 2))) 
    (triples 
      integers+ 
      integers+ 
      integers+))) 
 
(define (handler event context) 
  (let* ((n (string->number (hash-ref (hash-ref event 'queryStringParameters) 'length))) 
         (response (hash 'result (stream->list (stream-take pythagorean-triples n))))) 
    (hash 
      'headers (hash 'Content-Type "application/json") 
      'body (with-output-to-string (lambda () (write-json response)))))) 
handler 関数の中で、Eventオブジェクトから探索したいピタゴラス数の組み合わせの数を取り出して、計算結果を返しています。

これも、AWS SAM テンプレートから実行してみます。

template.yml
--- 
AWSTemplateFormatVersion: '2010-09-09' 
Transform: 'AWS::Serverless-2016-10-31' 
Resources: 
  SampleFunction: 
    Type: 'AWS::Serverless::Function' 
    Properties: 
      FunctionName: scheme-sample 
      Handler: function.rkt:handler 
      Runtime: provided 
      CodeUri: . 
      MemorySize: 1024 
      Timeout: 600 
      Layers: 
        - <Lambda Layer ARN> 
      Events: 
        API: 
          Type: Api 
          Properties: 
            Path: /pythagorean-triples 
            Method: get 
aws cloudformation package \ 
  --template-file template.yml \ 
  --output-template-file packaged-template.yml \ 
  --s3-bucket ${S3_BUCKET} 
aws cloudformation deploy \ 
  --template-file packaged-template.yml \ 
  --capabilities CAPABILITY_IAM \ 
  --stack-name ${STACK_NAME} 
試しに実行してみましょう。

curl https://API_GATEWAY_ENDPOINT/Prod/pythagorean-triples?length=3 
 
#=> {"result":[[3,4,5],[6,8,10],[5,12,13]]} 
これで、Lambda で SICP できるようになりました。

サーバー管理が不要で、無限にスケールし、使った分だけ課金の、最強のサーバーレス SICP です :innocent:


おわりに

Scheme Runtime の残念な点として、ちゃんと使える AWS SDK がほとんどありません。

(というか、デフォルトで提供されていない言語で公式にAWS SDKが提供されているものは PHP と C++ くらいです)

AWS サービスにまともにアクセスできない Lambda 関数をわざわざ使う状況というのはかなり限られそうです。

ただ、特定の用途に特化した言語をピンポイントで使えるのは面白そうな気がします。

基本的には Java で、絶対にバグを出したくないAPIは Haskell で、数値計算したいところは Python で、SICP したいところは Scheme で、とかとか…

今後どんな事例が出てくるのが楽しみです :smile:

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)