Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

SwiftにはREPLモードが2つある!?

スマフォ開発チームの高地です。

iOS開発界隈ではSwiftに注目が集まっています。 iOS8からはObjective-Cから解放されることで、喜んでいる開発者も多いのではないでしょうか。 ということで、今回はSwiftのREPL機能についてお話しします。

Swiftの2つのREPLモード

SwiftにはREPLが用意されているので、 Ruby、Python、Node.js等のLL言語のように簡単に動作確認ができます。 ところがSwift --helpをみていたところ、実はREPLモードが2つあることにきがつきました。

  • -integrated-repl Integrated REPL mode
  • -repl REPL mode

たぶん、-replは普通一般的なREPLモードだと思われます。それでは-integrated-replモードとはどんなことができるのでしょうか? 調査環境はXcode6-Beta2です。

SwiftのREPL:-integrated-replモード

まずは-integrated-replモードに切り替え、対話モードに入ります。実際にこのモードのオプションはどのようなものがあるのか:helpでたたいてみました。

:quit - quit the interpreter (you can also use :exit or Control+D or exit(0))
:autoindent (on|off) - turn on/off automatic indentation of bracketed lines
:constraints debug (on|off) - turn on/off the debug output for the constraint-based type checker
:dump_ir - dump the LLVM IR generated by the REPL
:dump_ast - dump the AST representation of the REPL input
:dump_decl  - dump the AST representation of the named declarations
:dump_source - dump the user input (ignoring lines with errors)
:print_decl  - print the AST representation of the named declarations
:print_module  - print the decls in the given module, but not submodules

-integrated-replモードには9つのオプションが用意されています。 :quite、:autoindentは想像がつくので省略し、1つずつ見てみましょう。

constraints debug

:constraints debug (on|off) - turn on/off the debug output for the constraint-based type checker

XcodeのConstraintsのためのデバッグオプションの設定のようです。 ここでon, offをきりかえて、一般的なREPL modeでデバッグを行うのでしょうか・・・デバッグ環境を用意するのも手間なので、これ以上は時間をかけないでおきます。

dump_ir

:dump_ir - dump the LLVM IR generated by the REPL

LLVMのIR(Intermediate Representation)ということで、LLVMでコンパイルされた中間コードを出力するようです、実際にREPLに入力してみます。MoenyForwardという文字列をdataというmutableな変数に代入するだけという1行コードです。

(swift) var data:String = "MoneyForward"
// data : String = "MoneyForward"  

たったこれだけではありあますが:dump_irで出力させてみると

swift) :dump_ir

; ModuleID = 'REPL'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.2.0"

%0 = type <{ %1 }>
%1 = type <{ %2, %3, %4 }>
%2 = type <{ i8* }>
%3 = type <{ i64 }>
%4 = type <{ [8 x i8] }>
%5 = type { i8**, %6 }
%6 = type { i64 }
%7 = type { void (%8*)*, i8**, %6 }
%8 = type { %6*, i32, i32 }
%9 = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i32, i32 }
%10 = type <{}>
%11 = type { [24 x i8], %6*, i8** }
%12 = type { i8*, %8* }
%13 = type <{ %12 }>
%14 = type { [24 x i8], %6*, i8** }
%15 = type <{ [40 x i8], [1 x i8] }>
%16 = type <{ [40 x i8], [1 x i8] }>
%17 = type <{ [40 x i8], [1 x i8] }>
%18 = type <{ [40 x i8], [1 x i8] }>
%19 = type { [24 x i8], %6*, i8** }
%20 = type { [24 x i8], %6*, i8** }
%21 = type { [24 x i8], %6*, i8** }
%22 = type <{ i8 }>
%23 = type <{ %24, %24 }>
%24 = type <{ i8* }>
%25 = type { [24 x i8], %6* }
%26 = type <{ %27 }>
%27 = type <{ %8*, i8*, %6* }>
%28 = type <{ i16 }>
%29 = type { %objc_object* }
%30 = type { %objc_object* }
%31 = type <{ [8 x i8] }>
%32 = type <{ %24, %33 }>
%33 = type <{ i64 }>
%34 = type <{ %35 }>
%35 = type <{ %36 }>
%36 = type <{ [8 x i8] }>
%swift.type_pattern = type opaque
%objc_object = type opaque
%swift.opaque = type opaque
%CSs17HeapBufferStorage = type opaque

@_Tv4REPL4dataSS = global %0 zeroinitializer, align 8
@0 = private unnamed_addr constant [13 x i16] [i16 77, i16 111, i16 110, i16 101, i16 121, i16 70, i16 111, i16 114, i16 119, i16 97, i16 114, i16 100, i16 0]
@1 = private unnamed_addr constant [20 x i16] [i16 47, i1


(省略)・・・


; 

かなりアセンブリチックな中間コードが約6000行ほど表示されました。 グローバルIDやローカルID(%swift.opaque)などが大量に出力されているのがわかります。 まさしく中間コードが出力されました。たった1行の宣言コードでしたが、コンパイルするための最適化のためでしょうか、非常に大量のコードが生成されているようです。

dump_ast

:dump_ast- dump the AST representation of the REPL input

これも上の:dumo_irと似ている印象を受けますが、コンパイル過程のAST(抽象構文木)が出力されます。

swift) :dump_ast

(source_file
  (top_level_code_decl
    (brace_stmt
      (tuple_expr type='()' location=:1:5 range=[:1:5 - line:1:6]))
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_typed type='String'
          (pattern_named type='String' 'data')
          (type_ident
            (component id='String' bind=type)))
        (call_expr implicit type='String' location=:1:19 range=[:1:19 - line:1:19]
          (dot_syntax_call_expr type='(RawPointer, numberOfCodeUnits: Word) -> String' location=:1:19 range=[:1:19 - line:1:19]
            (declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:19 range=[:1:19 - line:1:19] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no)
            (type_expr implicit type='String.Type' location=:1:19 range=[:1:19 - line:1:19] typerepr='<>'))
          (string_literal_expr type='(Builtin.RawPointer, numberOfCodeUnits: Builtin.Word)' location=:1:19 range=[:1:19 - line:1:19] encoding=utf16 value="MoneyForward")))
)
  (top_level_code_decl
    (brace_stmt
      (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1]
        (closure_expr type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] discriminator=0
          (brace_stmt
            (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1]
              (declref_expr implicit type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).print [with T=String] specialized=no)
              (call_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1]
                (dot_syntax_call_expr type='(RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1]
                  (declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no)
                  (type_expr implicit type='String.Type' location=:1:1 range=[:1:1 - line:1:1] typerepr='<>'))
                (string_literal_expr type='(Builtin.RawPointer, numberOfCodeUnits: Builtin.Word)' location=:1:1 range=[:1:1 - line:1:1] encoding=utf16 value="// data : String = ")))
            (call_expr implicit type='()' location=:1:1 range=[:1:1 - line:1:1]
              (declref_expr implicit type='(String) -> ()' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).debugPrintln [with T=String] specialized=no)
              (declref_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1] decl=REPL.(file).top-level code.explicit closure discriminator=0.arg@:1:1 specialized=no))))
        (paren_expr type='(String)' location=:1:1 range=[:1:1 - line:1:1]
          (load_expr implicit type='String' location=:1:1 range=[:1:1 - line:1:1]
            (declref_expr implicit type='@lvalue String' location=:1:1 range=[:1:1 - line:1:1] decl=REPL.(file).data@:1:5 specialized=no)))))
  (var_decl "data" type='String' storage_kind='stored'))

まだ、ASTのほうが、コード解析段階なので、人間の目にやさしいです。行数も36行程度なので、そこまで苦労せずにリーディングできそうです。

(declref_expr implicit type='String.Type -> (RawPointer, numberOfCodeUnits: Word) -> String' location=:1:1 range=[:1:1 - line:1:1] decl=Swift.(file).String._convertFromBuiltinUTF16StringLiteral specialized=no)

このあたりみると、内部的にUTF-16に変換していることが推測できます。

dump_decl

:dump_decl - dump the AST representation of the named declaration

は、AST内で定義されている変数をnameに指定することで、内容が確認できます。上記で書いたdata変数を指定してみると

(swift) :dump_decl data
(var_decl "data" type='String' storage_kind='stored')

とAST内での解析されたdata変数の内容が出てきます。var_declが変数定義を表し、typeが型、storage_kindはなんでしょう?変数が参照渡しなのか、値渡しを表しているのでしょうか、、、ちょっと不明ですね。

dump_source

:dump_source - dump the user input (ignoring lines with errors)

これは、ユーザーが入力したソースコードが出力されます。そのままですね。

(swift) :dump_source
var data:String = "MoneyForward"

そのまま表示されました。

:print_decl - print the AST representation of the named declarations

は、:dump_declオプション時よりももっと簡略的に出力されるようです

(swift) :print_decl data
var data: String

こちらはほぼ、素のSwiftコード定義に近い出力結果となりました。

:print_module - print the decls in the given module, but not submodule

は、Swiftで作成されたモジュールの内容を出力してくれるようです。実際にモジュールを作成してみればよいのですが、うまく作成できませんでした。何かよいモジュールはないか、色々とためしたところ、Swift自身を指定できるみたいです。

(swift) :print_module swift

を行うとSwift内部で定義されている、構造体、Extensions、プロトコル、など基底となる構造がずらずらとでてきます。何個かピックアップしますと、

struct UnsafePointer : BidirectionalIndex, Comparable, Hashable, LogicValue {
  var value: RawPointer
  init()
  init(_ value: RawPointer)
  init(_ other: COpaquePointer)
  init(_ value: Int)
  init(_ from: UnsafePointer)
  static func null() -> UnsafePointer
  static func alloc(num: Int) -> UnsafePointer
  func dealloc(num: Int)
  var memory: T {
    @transparent get {}
    @transparent set {}
  }
  func initialize(newvalue: T)
  func move() -> T
  func moveInitializeBackwardFrom(source: UnsafePointer, count: Int)
  func moveAssignFrom(source: UnsafePointer, count: Int)
  func moveInitializeFrom(source: UnsafePointer, count: Int)
  func initializeFrom(source: UnsafePointer, count: Int)
  func initializeFrom(source: C)
  func destroy()
  func destroy(count: Int)
  var _isNull: Bool {
    @transparent get {}
  }
  @transparent func getLogicValue() -> Bool
  subscript (i: Int) -> T {
    @transparent get {}
    @transparent set {}
  }
  var hashValue: Int {
    get {}
  }
  func succ() -> UnsafePointer
  func pred() -> UnsafePointer
  @conversion @transparent func __conversion() -> CMutablePointer
  func __conversion() -> CMutableVoidPointer
  @conversion @transparent func __conversion() -> CConstPointer
  @conversion @transparent func __conversion() -> CConstVoidPointer
  @conversion @transparent func __conversion() -> AutoreleasingUnsafePointer
  init(_ cp: CConstPointer)
  init(_ cm: CMutablePointer)
  init(_ op: AutoreleasingUnsafePointer)
  init(_ cp: CConstVoidPointer)
  init(_ cp: CMutableVoidPointer)
}

ポイントをキャストするためのUnsafePointer構造体や

    
extension Dictionary<KeyType, ValueType> : Printable, DebugPrintable {
  func _makeDescription(#isDebug: Bool) -> String
  var description: String {
    get {}
  }
  var debugDescription: String {
    get {}
  }
}
extension Dictionary<KeyType, ValueType> : Reflectable {
  func getMirror() -> Mirror
}

Dictionaryなどなどおなじみの構造が確認できます。ここから考えるに-integrated-replにおいて、Swift自身もモジュールというあつかいなのかなと。

ちなみに、対話モードにしなくても、出力結果をコンソール上に出力する方法もあります。shellのechoコマンドで、内部で実行したいオプションをわたしてあげても、同じ結果が得られます。

echo :print_module Swift | xcrun swift -integrated-repl

公式の情報が少ないので、本質的な-integrated-replの使い方はまだわかりませんが、このようにコンパイルする過程の状態(AST、IR)を容易に確認できる手段がREPLで用意されているのというのは驚きでした。Swiftを使う開発者が、より最適なコーディングを理解するための情報を得るツールとしても使えますね。 このような開発ツールの充実度を高めようとしている姿勢からAppleのSwiftにかける本気度を感じました。

マネーフォワードでは、他のエンジニアとは異なった視点で、問題を解決できる、マニアックなエンジニアも募集中です。