書いて覚えるSwift入門

第29回 順序どおり問題を制する

この記事を読むのに必要な時間:およそ 3.5 分

より実践的なSequence

Sequenceが真の威力を発揮するのは、単一の値ではなく複数の値をまとめて扱うときにあるのは、.map().filter().reduce()の例のとおりです。Swiftにはすでに標準でArrayを持っていますが、たとえば5,000兆個のデータをArrayに読み込んでというのはメモリが足りなさ過ぎるでしょう。そういう場合にもSequenceに準拠した型としてそれを実装すれば、少しずつストレージから読んで処理するのも「いつものやり方」で実現できます。

ここではLispやHaskellでよく用いられている片方向リストを実装してみましょう。こんな感じですか。

enum List<T> {
    case Nil
    indirect case Pair(head:T, tail:List<T>)
}

最低限のアクセサーと……、

extension List {
    var car:T? {
        get {
            switch self {
            case .Nil: return nil
            case let .Pair(v, _): return v
            }
        }
        set {
            switch self {
            case .Nil: return
            case let .Pair(_, l):
                self = List.Pair(head:newValue!, tail:l)
            }
        }
    }
    var cdr:List<T>? {
        get {
            switch self {
            case .Nil: return nil
            case let .Pair(_, l): return l
            }
        }
        set {
            switch self {
            case .Nil: return
            case let .Pair(v, _):
                self = List.Pair(head:v, tail:newValue!)
            }
        }
    }
}

………イニシャライザーを用意しておきます。

extension List {
    init(fromArray: [T]) {
        self = fromArray
        .reversed()
        .reduce(.Nil) {
            List.Pair(head: $1, tail: $0)
        }
    }
    init(_ values:T...) {
        self.init(fromArray:values)
    }
}

この状態で、

var l = List(0,1,2,3)

とすれば確かにl0->1->2->3->Nilという具合に初期化されて、print(l)してみると、Pair(head: 0,tail: __lldb_expr_80.List<Swift.Int>.Pair(head: 1, tail: __lldb_expr_80.List<Swift.Int>.Pair(head: 2, tail: __lldb_expr_80.List<Swift.Int>.Pair(head: 3, tail:__lldb_expr_80.List<Swift.Int>.Nil))))なんて感じになっているのですが、これをSequenceにするにはどうしたらよいのでしょう? 先ほどの例のようにListIteratorを追加しても良いのですが、実はSwiftには次のような簡単な方法も用意されています。

extension List : Sequence {
    public func makeIterator() -> AnyIterator<T> {
        var list = self
        return AnyIterator {
            let v = list.car
            if let l = list.cdr {
                list = l
            }
            return v
        }
    }
}

こうしてからfor v in l { print(v) }してみると、確かに値を頭から取れていることがわかります。

あとはこれを利用して、Array化したり……、

extension List {
    var asArray:[T] {
        return self.map{$0}
    }
}

もっと見やすいよう文字列化したりするのは楽勝です。

extension List : CustomStringConvertible {
    var description:String {
        return "List("
          + self.map{"\($0)"}
          .joined(separator:", ")
          + ")"
    }
}
print(List(0,1,2,3)) // "List(0, 1, 2, 3)"

次回予告

今回はSwiftがSequenceプロトコルを通してどのように「順序」というものを抽象化しているかを見ていきました。しかし世の中は順序どおりにいくとは限らないもの。その場合には、どうしていくのか。

次回はCollectionプロトコルを通してそれを見ていきます。

Software Design

本誌最新号をチェック!
Software Design 2022年6月号

2022年5月18日発売
B5判/184ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    シェルの基本大全
    どんな環境でも迷わない・困らない知識
  • 第2特集
    後悔しないAWSデータベースの選び方
    RDSとDynamoDB,使い分けのポイントを徹底解説
  • 第3特集
    不思議の国のSE用語
    一人前のエンジニアになる方法
  • 短期連載
    新生「Ansible」徹底解説
    [1]Ansibleとは
  • 短期連載
    HashiCorp Vaultではじめるシークレット管理
    [最終回]KubernetesからVaultを利用しよう

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。