@m_seki の

I like ruby tooから引っ越し

lambdaのノート、Ruby編

AWSのlambdaのこと

AWS lambdaでRubyが使えるようになったそうなので、いつまでもlambdaを避けて通れなくなってきました。(自分に言い訳しづらくなてきた) 仕事でもそれ以外でも使ってないし、AWSしぐさが全然わからんのです。ぐぬぬ

最終的にやりたいこと

CGIで済むようなものを作れるようになっておきたい。(池澤さんのための素振り)

準備

はじめてのAlexaスキル開発 [音声認識アプリ開発の基礎知識を身に付ける! ]

はじめてのAlexaスキル開発 [音声認識アプリ開発の基礎知識を身に付ける! ]

アレクサの本を読んでAWSアカウントの作成やら、lambdaの簡単なサンプルは実験してありました。 この本、サンプルアプリは少ないんだけど、アカウントの作成が詳しくて重宝しましたよ!

今日やったこと

gemとかアーカイブしてアップロードして...みたいな記事を見てやってたけどうまくいかなくて(今は原因わかったけど)、基本に戻ろうと思って次の関数書きました。 ハンドラに渡される引数をppして確認するだけのlambdaです。

lambdaの設定画面から新規の関数を作って埋めただけ。

require 'pp'

def lambda_handler(event:, context:)
    body = { 'event' => event, 'context' => context }.pretty_inspect
    
    {
        'statusCode' => 200, 
        'headers' => { 'Content-Type' => 'text/plain'},
        'body' => body
    }
end

トリガーの左のペインからAPI Getewayっていうのを選んでいろいろ適当にやった結果動くようになりました。

https://8zmwlgdxf9.execute-api.ap-northeast-1.amazonaws.com/default/pp_inspect

実行すると引数のオブジェクトを観察できます。URLの末尾にパスを連結したり、クエリを追加したりしてHashがどう変わるか調べることができました。 WEBrick::CGIやTofuにこのインターフェイスを吸収するレイヤーを用意するのがかっこいいんだろうけど、どうせ誰かやるだろうから待ってよう。

ところで、URLから「default/」(ステージ?)っていうのを消したいんだけど...(よくわからないのでそのまま...)。

最後にリクエストの表現がわかってきたので、EAN(JAN)コードを与えると、その商品のamazonのページに飛ぶ関数を書いてみました。 クエリに「ean=値」をつけると該当商品のページが表示されます。

https://qtmf64s8p8.execute-api.ap-northeast-1.amazonaws.com/default/myFirstRuby?ean=4521329227023

合わせて読みたい

ラムダノートのラインナップより。

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

通販でスタッドレスを買ったよ!

去年のネタの続き。

druby.hatenablog.com

 

このときは結局買わなかったんだけど、今年はついに通販でスタッドレス買ったよ!ホイールなし、タイヤのみです。佐々木くんがWINTER MAXX02を使っているそうなので、参考にお安いWINTER MAXX01にしました。私のゴルフ7トレンドラインのサイズは195/65R15。たぶんプリウスとかも同じようなサイズじゃないかな。4本で36000円でした。

 

 

いつものVWのディーラーにお願いして組み替えしてもらいます。作業の費用は廃棄費用等含めて一本2500円前後になると思う。(前回はそうだった。)

 

今回は今までのホイールがあったからそれを使うのけど、クルマを買ったばかりで新たに買うなら、ホイール付きのタイヤの方がお買い得だと思う。バランス取りした状態で送られてくるから取り付けるだけ。

 

HKQueryAnchorの永続化についてのメモ

HKQueryAnchorの永続化のメモだよ

先日のHealthKitの練習のときのメモ。HealthKitのデータには登録順にアンカーと呼ばれる整数のようなものがつけられています。これを使うと前回読んだ事実(データ)よりもあとに増えた事実を集める処理が書きやすいです。Dripのキーに近い。

つまづき

アンカーはHKQueryAnchorのインスタンスです。作るときはこんな風にしました。

myAnchor = HKQueryAnchor.init(fromValue: 0)

整数から作れるのね。よしよし。整数にして保存しようっと...と思って困ったのは整数化するインターフェイスがないところ。
NSLogとかでデバッグプリントするときは内部の整数風の値を見ることができるけど、整数を取ることはできないみたい。

これはめんどくさい予感。

永続化

HKQueryAnchorから整数を取り出すのではなく、NSKeyedArchiver.archivedDataを使うのが正解みたい。UserDefaultsに保存するときはこうか?

    func saveAnchor() {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: self.myAnchor, requiringSecureCoding: true)
            let defaults = UserDefaults.standard
            defaults.set(data, forKey: "anchor")
        } catch {
            NSLog("encode failed")
        }
    }

失敗してもできることがないから、catchしなくてもいいくらいなんだけどどうするのが良いんだろ。

読み出しも同じようにした。

    func initHK() {
        // ...
        myAnchor = HKQueryAnchor.init(fromValue: 0)
        let defaults = UserDefaults.standard
        guard let it = defaults.data(forKey: "anchor") else { return }
        do {
            myAnchor = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(it) as? HKQueryAnchor
        } catch {
            // nop
        }
    }

まだSwiftの?や!やtryの気持ちがわかってない。コード片も自信がない。

まとめ

自分なら整数か文字列にすると思う。けち。

HealthKitのデータのAnchorがDripみたいだった。凡庸。

Skagen Hybridを買いましたが

エルメスApple Watchを自慢されることが多いため、Skagen Hybridを買いました。
が、アクティブカロリーの単位(Cal = kcal と cal)を混同するバグが組み込まれていました。具体的に言うと1kcalが1cal(0.001kcal)として記録されてます。一日経っても1kcalを超えません。*1

解決策

どうしよう。思いつくのは以下の選択肢。

  1. 気にしない - iOS上のヘルスケアで値を目で読むときに1000倍して受け取ればいい。
  2. バグを報告して直してもらう - 治る気配ない
  3. データを修正する - じゃあこれで

データを修正するアプリを書くことにしました。

アプリの概要

アプリのやること。
HealthKitのアクティブエネルギーのログのうち、メタデータに記録されてるデバイスが「Fossil device」のエントリーを集めます。
このエントリーから開始時刻と終了時刻を転記し、カロリーを1000倍したデータをつくって、ログに書き加えます。
(念の為、万が一Skagenのアプリが修正されたときに備えて、1kcalを超えるエントリーがあったら無視するようにしました。)

ログを取得にはHKAnchoredObjectQuery()っていうAPIを使いました。これは、データベースのエントリーの発生順に振られるアンカー(単調増加の整数らしきもの)をヒントに、以前読んだ位置より新しいデータを読むことができるもので、Dripとよく似ています。

ソースはこれ。200行いかない。

github.com

どこで買うべきか

スカーゲン(SKAGEN) ユニセックス ハイブリッドスマートウォッチ HAGEN HYBRID(ハーゲン ハイブリッド) 【ブラック×ブラック/**】

↑これじゃないけど似てるやつ。Skagenの公式の通販で買ったけど、那須のアウトレットだと半額以下で買えること知りました。とほほ。
なお4ヶ月くらい使ったけど電池交換しなくていいのがすごい!

まとめ

Apple Watchが欲しいです。

*1:他のFossil製品にも同じバグがあるかもしれません。

XP祭りに行ったよ、そういえば

XP祭りに行って、基調講演を聞いて、カレーを食べて、ワークショップをやりました

もうずいぶん前のことのような気がしますが、久しぶりに(前回の基調講演以来)XP祭りに参加しました。
元々は とてか でやったあのチームのセッションの、フレーズを再演してみたい!ということで計画しました。

基調講演

基調講演は id:t-wada さん!!審美眼の再演を予想してたけど、TDDのお話でした。予想を裏切られてうれしい。なんというか、ずっと考えながら聴けるさいこーな講演でした。今まで聞いたt-wadaのなかで1番好きだな、たぶん。

カレー

大人だからお昼はタクシーに乗ってカレー屋さんへ。タクシーがお店に近づくにつれ確信したけど、このカレー屋さんは二度目だよ!この前のXP祭りのときに食べたとこ。おいしかった。

Cafe GOTO

ワークショップまで時間があったので、おなかいっぱいだけどカフェへ。Cafe GOTO。このgotoはいいgoto。ケーキが美味しそうだったなー。

ワークショップ

おなじみのフレーズの再演です。前座がない状態で始めたので、声を出してもらえるか心配でしたが、まあまあうまくいったかなー。こしばさん、jug-wadaさんとかに参加してもらえたのがうれしい(やりやすかった)。次はどこかの職場(チームとか)にまじってフレーズやりたいなー。ESMで反復開発の再演したときはとてもいい感じだったなー。


去年からやってた「反復開発」の再演、またやりたいです。後半のストーリー(チケット)の辺りを膨らませたやつを話したいな。

追記

90日ブログを書かないと広告が出てしまうので、思い出しエントリー書きました。

RubyKaigi 2018でRubyでつくるRubyを紹介して売り切った件

RubyKaigi 2018

仙台行ってきた!楽しんでいただけたでしょうか。
太助 本店、太助 分店、山形ラーメン、大漁旗(笹かま)、ホテルの朝食、そしてマネーフォワード提供の会場のランチがとても心に残っています。

今回はMinRubyを改造して自分用の言語作ろうって話でした。最後はこういう感じになります。

今回は直前に とてか05 があったり英訳問題があったりほんとに準備不足になってしまいました。説明飛んじゃったところや滑舌が死んだところがあったりして申し訳ない。
発表はうまくいかなかったけど、会場で売ってた「RubyでつくるRuby」を売り切れたので満足です。うれしー。なお、幸福の王子本も売り切れたのすごい!みんなもつきあいで買おう!

技術書出版ラムダノート株式会社 – 技術書出版と販売のラムダノート

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

dRubyによる分散・Webプログラミング

dRubyによる分散・Webプログラミング

スライドはspeakerdeckで公開してます。
(階乗のコード間違ってるぽい!)

speakerdeck.com

新幹線代をCookpadに、その他の交通費や宿やもろもろの経費はスライドスポンサー、pixivFANBOX、people.suzuriのみなさんに支援していただきました。
ありがとうございます。Let's自転車操業
来年は福岡か...前後の日程で再演に呼んでくれる会社とかないだろか。来年でなくても福岡予習に行きたい。

あわせて課金したい

スライドスポンサーはまだ課金できます!(そしたらspeakerdeck更新します)

恒例!スライドに名前を入れる券 2018-2019
m_seki[pixivFANBOX]
咳 ∞ SUZURI People

minrubyにDripをつける

minrubyにDripのSTMぽいやつをつける実験 1

Dripという自分しか使ってないライブラリがあります。追記しかできないストリーム型のストレージで、某所のRWiki全文検索や画像アップローダーのバックエンドで使われています。
全てのデータには時刻から計算されるユニークな識別子がつき、さらに重複可能なタグ(名前)も設定できます。
タグをキーに見立てると、バージョンつきのキーバリューストアのようにも使えます。

DripはgithubにあるしThe dRuby Bookにもあるので見てね。


アプリケーションがあるキーの値を参照し、計算し、格納するケースを想像してください。計算のために参照した値が、格納するまでに変更されていたとしたら、その計算は無効になってしまいます(というユースケースを妄想しよう!)。
そこでアプリケーションが値を参照する際に、そのバージョン番号をメモしておき、格納するときにそのバージョン番号が最新であるか確認できれば、計算をやり直すことができるはずです。

Dripの書き込みにはwrite_if_latestという変わったメソッドがあります。あるタグの最新のデータの識別子と、引数で与える条件の組みとを比較して、一致したときだけ書き込みが成功するメソッドです。
つまり任意のキーのバージョンをチェックしてから書き込むプリミティブです。バージョンチェックと書き込みはアトミックに行われるので、バージョンチェックと書き込みの間に、別のコンテキストが書き込むことはできません。
Dripのサーバーにはトランザクション的な状態は一切持たずに、似たようなことができるのでお得です。

こんな感じです。(MyDripはDripに入ってるおまけ)

require 'my_drip'

MyDrip.invoke

def root
  Root.new
end

class Root
  class VersionMismatchError < RuntimeError; end
  def initialize
    @visited = {}
  end

  def visited
    @visited.collect do |key, age|
      [key, age]
    end
  end

  def [](key)
    found ,= MyDrip.head(1, key)
    age, value, = found
    @visited[key] = age || 0
    value
  end

  def []=(key, value)
    it = MyDrip.write_if_latest(visited, value, key)
    raise(VersionMismatchError) if it.nil?
    @visited[key] = it
    value
  end
end

if __FILE__ == $0
  3.times do |n|
    Thread.new(n) do |x|
      root = Root.new
      10.times do
        begin
          count = root['count'] || 0
          sleep(rand * 3)
          p [root['count'] = count + 1, x]
        rescue
          p [:retry, x]
          retry
        end
      end
    end
  end

  gets
end

この仕組みは前からあって意識して書けばうまく機能します。

minrubyに入れる

(ちょっと苦しいんだけど)意識しなくても使えるように考えてみました。minrubyでデータベースの更新をするだけのプログラムを書いたとして、更新に矛盾が起きそうなときはプログラム全体を再起動する、ってことにします。

db = root()

count = db['count'] = db['count'] + 1
p count

たとえばこういうやつ。root()でデータベースのインターフェイスを手に入れて、db['count']の値に+1して保存します。もしも参照したdb['count']が別のプロセスによって変更されていたら、プログラム全体を再起動します。

もとのminrubyインタプリタの最後の方を次のように変更すればできそう。ただし、minrubyで実装されていないrescueやらretryやらがあるので、minruby自身でminrubyを実行することはできなくなります。そこは我慢。

str = minruby_load()
begin
  tree = minruby_parse(str)

  genv = {
    "p" => ["builtin", "p"],
    "require" => ["builtin", "require"],
    "minruby_parse" => ["builtin", "minruby_parse"],
    "minruby_load" => ["builtin", "minruby_load"],
    "minruby_call" => ["builtin", "minruby_call"],
    "root" => ["builtin", "root"]
  }
  lenv = {
  }
  evaluate(tree, genv, lenv)
rescue Root::VersionMismatchError
  p :retry # 観察用
  retry
end

なお実行が速過ぎてなかなか「やりなおし」が起こせないので、evaluate()の冒頭でsleepさせることにしました。

def evaluate(tree, genv, lenv)
  sleep(rand * 0.3)
  case tree[0]

全体のコードはgistにあります。

minruby + STM · GitHub

シェルを駆使して(すみません。コピペしました。もっとうまくかけるはず)同時にたくさんプロセスを起動するとretryする様子が見えます。

$ (ruby -I. stm-interp.rb test-1.rb &) ; (ruby -I. stm-interp.rb test-1.rb&) ;(ruby -I. stm-interp.rb test-1.rb&) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb &) ;(ruby -I. stm-interp.rb test-1.rb&) 
$ :retry
:retry
:retry
:retry
:retry
31
30
32
33
:retry
:retry
34
35
36
37

テストプログラムに10回ループしてdb['count']を更新する例があります。これを二つのプロセスで実行すると、矛盾を検知するたびにプログラム全体が再起動されてしまい、10回のループをやり直すので、運が悪いとやり直しが交互に発生してなかなか終われなくなります。賽の河原の石積みを連想させる、ダメなサンプルでした。


gist.github.com