Logo
  • DoRubyとは
  • お問い合わせ
  • Ruby/Rails
  • Web開発
  • Webマーケティング
  • アピリッツ
  • ライフハック
  • ゲーム制作/開発
  • アプリ開発
  • 新しいカテゴリ
  • Railsの小技
  • Rubyの小技
  • Gemの紹介
  • ActiveRecord
  • 初心者向け
  • Java/Scalaテク
  • Unixのあれこれ
  • データベース
  • スマホ開発
  • HTML/CSS/JavaScript
  • デザイン製作
  • インフラ
  • クラウド
  • セキュリティ
  • エディタ
  • バージョン管理
  • その他
  • python
  • アクセス解析
  • Googleアナリティクス
  • Googleデータスタジオ
  • Web広告
  • SEO
  • UI/UX
  • ソーシャルメディア
  • EC開発
  • Webシステム開発
  • コンサルティング
  • Webデザイン
  • ブロックチェーン
  • ゲーム紹介
  • アプリ紹介
  • ASP
  • 風景
  • パソコン
  • ツール
  • ガジェット
  • 仕事術
  • 健康
  • 生活
  • 書評
  • Excel(エクセル)
  • PowerPoint(パワーポイント)
  • ゲームプランニング
  • SpriteStudio
  • マスターデータ入力/作成
  • Unity
  • キャラクターデザイン
  • ゲームシナリオ
  • レベルデザイン
  • ゲーム分析
  • 3DCG
  • イラスト制作
  • CG/アニメーション
      1. ホーム
      2. Ruby/Rails
      3. Rubyの小技
      4. Ruby黒魔術【callcc】でFiberを実装してみた
      • 2018-02-28
        • カテゴリ:
        • Rubyの小技
        • タグ:
        • callcc
        • Fiber
        • ruby

      Ruby黒魔術【callcc】でFiberを実装してみた

      C76e0afda16cfaa5617a4d025435cab0

      以前rubyの大域脱出を紹介した際にrubyには継続渡しがあると話をしたので、簡単に解説してみた上で継続渡しとFiberの理解のためcallccを使ったEvilFiberを実装してみました。

      はじめに

      17新卒エンジニアくろすです。
      使ってたBluetoothイヤホンが断線したり、代わりに通販で買ったイヤホンが初期不良で電源入らなかったりと本厄の洗礼が激しく、お祓いに行こうか真剣に悩んでます。
      誰かオススメの厄払いがある方いらっしゃいましたら連絡をください。

      今回は楽しい黒魔術の話だけ書いてパパッと終わらせちゃうぞ、と思っていました。
      思っていました。
      callccからFiberの話なんか始めちゃったのが間違いでした。

      環境等

      環境 : ruby-2.4.2
      callccを使おうとcontinuationをrequireすると

      warning: callcc is obsolete; use Fiber instead

      と怒られます。

      処理の中断、再開として使うなら Fiber
      大域脱出として使うなら throw..catch
      があるのでそちらを使いましょう。

      黄泉還りを楽しみたい方は遊びの範囲内で使いましょう。

      黒魔術

      メタプログラミングによる動的なコード生成や加工が黒魔術と呼ばれることが多いです。
      Rubyで有名なのは
      与えられた文字列をrubyのコードとして解釈するようなeval属
      動的なメソッドを生成、またはそのメソッドが存在するかのように見せかけるdefine_methodやmethod_missingによるゴーストメソッド
      などがあります。
      今回はRuby黒魔術の中でも黄泉還りが危険すぎてrequireしないと使えないようになってしまっているcallccについて軽く触れていきたいと思います。

      Rubyの継続

      require 'continuation'
      
      x = 1
      if x == 1
        y = x * callcc{|cont| @a = cont; x}
        puts y
      end
      if y < 5
        x += 1
        @a.call(x)
      end
      

      callccを呼ぶことでcallcc以降のコンテキスト(継続)を@aに保存して、@a.call(x)でcallcc{|cont| @a = cont; x}の返り値に、call時のxの値を代入して5行目に戻ります。
      パッと見、yにはx2 が入りそうな感じがしますが、Ruby的には最初に5行目を評価した時点で、xの値も評価し終わっているようで結果は以下のようになります。

      $ ruby black_magic.rb
      1
      2
      3
      4
      5
      

      一歩間違えば無限ループ入りまっしぐらなのが見てわかるかと。

      ちなみに、5行目を以下のように変更すると

      y = callcc{|cont| @a = cont; x} * x
      

      結果はこうなります。

      $ ruby black_magic.rb
      1
      4
      9
      

      ざっくり継続の感じが掴めましたでしょうか。

      Fiber

      話が少し飛びますが、そもそもFiberとはなんぞ?という疑問がありそうなので軽く引用します。

      ノンプリエンプティブな軽量スレッド(以下ファイバーと呼ぶ)を提供します。 他の言語では coroutine あるいは semicoroutine と呼ばれることもあります。 Thread と違いユーザレベルスレッドとして実装されています。

      引用 : Ruby2.5.0 Fiberクラスリファレンス

      Rubyはそもそもの言語仕様として同時に実行されるネイティブスレッドは常にひとつだったりするんで、あくまでRubyVM内でノンプリエンプティブだって話ですが......
      参考 : Ruby2.5.0 Threadクラスリファレンス

      もう完全にコルーチンなんですが、要するにrubyのコードレベルで明示的にコンテキストを変更できる軽量なスレッド(スレッドではない)です。
      Ruby的にわかりやすく言うならば処理の中断再開が可能なProcオブジェクト的な感じです。
      以下のようにfiber自体は同一スレッド内でコンテキストを変更して動きますし、別スレッドからresumeしようとするとFiberErrorって怒られます。

      require 'fiber'
      
      fiber =
        Fiber.new do
          loop do
            Fiber.yield Thread.current.object_id
          end
        end
      
      puts <<-EOS
      ===========================
        parent : #{Thread.current.object_id}
        child  : #{fiber.resume}
      ===========================
      EOS
      #=>
      # ===========================
      #   parent : 70103219828660
      #   child  : 70103219828660
      # ===========================
      

      EvilFiber

      callccを使い実装した邪悪なFiberです。
      このブログのcounterを参考にさらに抽象度を上げてFiberに近づけた形になります。
      元記事でも言われてますが、stopとresumeのスパゲッティ具合がとにかく邪悪です。
      限界までFiberとfiber(コルーチンライクに使うための標準ライブラリ)に合わせようと思いましたが、#transferとスレッドセーフを実装する気力はなかったです......

      require 'continuation'
      
      class EvilFiber
        @@fibers_hash = {}
        @@current = nil
        @@prev = nil
      
        class << self
          def yield(*args)
            @@current.stop(*args)
          end
      
          def current
            @@current
          end
      
          def prev
            @@prev
          end
      
          private
          def fibers_hash
            @@fibers_hash
          end
        end
      
        def initialize
          @@fibers_hash[self.object_id] = self
          @resume =
            proc do
              yield
              @dead = true
              reset_registered_fiber
              nil
            end
        end
      
        def stop(*args)
          reset_registered_fiber
          callcc do |cont|
            @resume = cont
            @return.call(*args)
          end
        end
      
        def resume
          raise 'THIS FIBER DIED!!!' if self.dead?
          @@current = @@fibers_hash[self.object_id]
          callcc do |ret|
            @return = ret
            @resume.call
          end
        end
      
        def alive?
          not @dead
        end
      
        def dead?
          !!@dead
        end
      
        private
        def reset_registered_fiber
          @@prev, @@current = @@current, nil
        end
      
      end
      
      [1] pry(main)> require_relative 'evil_fiber'
      warning: callcc is obsolete; use Fiber instead
      => true
      [2] pry(main)> f = EvilFiber.new{ puts '小烏丸'; EvilFiber.yield; puts '蜥蜴丸'; EvilFiber.yield; puts '小狐丸' }
      => #<EvilFiber:0x00007fc61a0e7dc8 @resume=#<Proc:0x00007fc61a0e7d50>>
      [3] pry(main)> f.resume
      小烏丸
      [4] pry(main)> f.resume
      蜥蜴丸
      [5] pry(main)> f.resume
      小狐丸
      [6] pry(main)> f.resume
      RuntimeError: THIS FIBER DIED!!!
      

      new時に渡しているブロック内のEvilFiber.yieldをf.yieldにするという実装もあったのですが、コードとして見たときにパッと見未生成に見えるオブジェクトを触っているのが気持ち悪いので、Fiber側に合わせる方向にしました。
      実際渡してるブロックが評価されるのはブロックがcallされた時(つまりこのコードだと最初にresumeが呼ばれた時)で、mainのオブジェクトのBindingから変数fを引っ張ってくるはずなので動くと思うんですが気持ち悪いものは気持ち悪いです。
      オブジェクトの生成とブロックの登録を分ければ多少気持ち悪さが解消される気がします。

      終わりに

      ざっくりとですが、callccとFiberの勉強ができていい機会になったなって思います。
      特にFiberですが、Threadよりコードに寄ってくれるので個人的にはかなり楽に書けるなと印象でした。
      マルチスレッドプログラミングじゃなくてただのコルーチンと化した使い方しかしていないのでそりゃそうですけど。
      一度ちゃんとマルチスレッドプログラミング勉強しないとなぁ......


      • 126 views
        • Tweet
        • このエントリーをはてなブックマークに追加

      この記事を書いた人
      F9cd312d5609e22b8636843d05b0ffb6?default=mm&size=80
      くろす
      17/AG/newbie

      「いいね!」するとDoRubyの最新記事を受け取ることができます。

      Facebook

      Twitterから最新記事を受け取るならこちら

      Follow @doruby

      Feedlyから最新記事を受け取るならこちら

      follow us in feedly

      おすすめの記事
      • 502 views
      • 2015-10-01
      RailsのViewを自在にカスタマイズするための「Cosme」gem
      • 1,640 views
      • 2016-07-27
      リニューアルをしたDoRubyの3つの目的
      • 685 views
      • 2016-07-31
      carrierwaveとfogでRiak CSへの画像アップロードを実装する
      • 1,103 views
      • 2017-04-24
      アピリッツの新卒合宿2017

      カテゴリ

      Icon category 1Ruby/RailsList elementRailsの小技List elementRubyの小技List elementGemの紹介List elementActiveRecordList element初心者向けIcon category 4Web開発List elementJava/ScalaテクList elementUnixのあれこれList elementデータベースList elementスマホ開発List elementHTML/CSS/JavaScriptList elementデザイン製作List elementインフラList elementクラウドList elementセキュリティList elementエディタList elementバージョン管理List elementその他List elementpythonIcon category 5WebマーケティングList elementアクセス解析List elementGoogleアナリティクスList elementGoogleデータスタジオList elementWeb広告List elementSEOList elementUI/UXList elementソーシャルメディアIcon category 6アピリッツList elementEC開発List elementWebシステム開発List elementコンサルティングList elementWebデザインList elementブロックチェーンList elementゲーム紹介List elementアプリ紹介List elementASPList element風景Icon category 7ライフハックList elementパソコンList elementツールList elementガジェットList element仕事術List element健康List element生活List element書評List elementExcel(エクセル)List elementPowerPoint(パワーポイント)List elementゲーム制作/開発List elementゲームプランニングList elementSpriteStudioList elementマスターデータ入力/作成List elementUnityList elementキャラクターデザインList elementゲームシナリオList elementレベルデザインList elementゲーム分析List element3DCGList elementイラスト制作List elementCG/アニメーションList elementアプリ開発List element新しいカテゴリ

        人気の記事
        • 27 views
        • 2019-02-12
        カスタマーへの活用促進とヘルススコア
        最近の記事
        • 27 views
        • 2019-02-12
        カスタマーへの活用促進とヘルススコア
        • 115 views
        • 2019-01-08
        日本でもっとカスタマーサクセスを盛り上げよう!!の年末イベント
        • 177 views
        • 2018-12-10
        【Rails】I18nの言語データをDBから取得 / キャッシュ使用【i18n/i18n-active_record】
        • 165 views
        • 2018-12-10
        【Rails】I18nの言語データをDBから取得【i18n/i18n-active_record】
        • 148 views
        • 2018-11-12
        【jQuery】開閉パネルを最初から開いた状態にする
        Facebook

          サイト情報
          • DoRubyとは
          • 株式会社アピリッツ

          企業情報
          • 会社概要
          • 採用情報
          • お問い合わせ

          ソーシャルアカウント
          • Facebook
          • Twitter
          サービス製品
          • コミュニティサイト
          • Android向け ECアプリ制作パッケージ「ポケコマ」
          • レコメンドASP
          • サイト内検索ASP「Advantage Search」
          • オープンソースECサイト構築パッケージ「エレコマ」

          • 受注・在庫・商品情報一元管理「モールコネクター」
          • セキュリティ診断サービス
          • 戦略的EC画面設計・制作サービス
          • Googleアナリティクスセミナー
          • アクセス解析コンサルティング
          • SEOコンサルティング

          Copyright © Appirits All Rights Reserved.