情報系人間のブログ

プログラミング、開発に関することを書いていきます。

Swiftで関数型データ構造

Swiftで関数型のデータ構造を作ってみます。 関数型データの構造は永続的であるという特徴があります。なので更新する際には既存のデータを破壊するのではなく新しいオブジェクトを作ります。Enumを使ってこのリストを作ってみます。 consはリストの先頭、nilはリストの終端を表します。

enum List<T: Equatable> {
    case `nil`
    indirect case cons(T, List<T>)
}

またリストは比較できると便利なのでEquatableを満たすようにします。

extension List: Equatable {

    static func ==(lhs: List, rhs: List) -> Bool {
        switch (lhs, rhs) {
        case (.`nil`, .`nil`):
            return true
        case (.cons(let h, let t), .cons(let h1, let t1)):
            return h == h1 && t == t1
        default:
            return false
        }
    }
}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
let list1 = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
let list2 = List<Int>.cons(1, .cons(2, .cons(3, .cons(4, .`nil`))))
list == list1 // true
list == list2 // false

CustomStringConvertible を満たすようにすると出力が見やすくなるのでやっておきましょう

extension List: CustomStringConvertible {

    var description: String {
        switch self {
        case .`nil`:
            return "nil"
        case .cons(let head, let tail):
            return "\(head),\(tail)"
        }
    }
}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
print(list) //1,2,3,nil

リストなら以下のような要素の数を取得できるようにしたいですね。やってみましょう。

extension List {

    var isEmpty: Bool {
        switch self {
        case .`nil`:
            return true
        case .cons:
            return false
        }
    }

    var head: T? {
        switch self {
        case .`nil`:
            return nil
        case .cons(let head, _):
            return head
        }
    }

    var tail: List<T>? {
        switch self {
        case .`nil`:
            return nil
        case .cons(_, let tail):
            return tail
        }
    }

    var length: Int {
        switch self {
        case .`nil`:
            return 0
        case .cons(_, let tail):
            return 1 + tail.length
        }
    }

    var last: T? {
        switch self {
        case .`nil`:
            return nil
        case .cons(let head, .`nil`):
            return head
        case .cons(_, let tail):
            return tail.last
        }
    }

}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
list.isEmpty // false
list.head // 1
list.tail // 2,3,nil
list.length // 3
list.last // 3

要素の追加、削除はできて当然ですね。これも実装していきます。 だいたいやっている事は同じです。リストの要素を辿っていき該当の要素に対して処理を行っています。

extension List {

    func add(value: T) -> List<T> {
        switch self {
        case .`nil`:
            return List<T>.cons(value, .`nil`)
        case .cons(let head, let tail):
            return .cons(head, tail.add(value: value))
        }
    }

    func remove(index: Int) -> List<T> {
        switch (self, index) {
        case (.`nil`, _):
            return self
        case (.cons(_, let tail), 0):
            return tail
        case (.cons(let head, let tail), let index):
            return .cons(head, tail.remove(index: index - 1))
        }
    }

    func update(index: Int, value: T) -> List<T> {
        switch (self, index) {
        case (.`nil`, _):
            return self
        case (.cons(_, let tail), 0):
            return .cons(value, tail)
        case (.cons(let head, let tail), let index):
            return .cons(head, tail.update(index: index - 1, value: value))
        }
    }

    func append(list: List<T>) -> List<T> {
        switch self {
        case .`nil`:
            return list
        case .cons(let head, let tail):
            return List.cons(head, tail.append(list: list))
        }
    }

}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
list.add(value: 100) // 1,2,3,100,nil
list.remove(index: 1) // 1,3,nil
list.update(index: 0, value: 100) // 100,2,3,nil
list.append(list: list) // 1,2,3,1,2,3,nil

最後に高階関数を実装します。

extension List {

  func foldr<R>(acc: R, f: (T,R) -> R) -> R {
      switch self {
      case .`nil`:
          return acc
      case .cons(let head, let tail):
          return f(head, tail.foldr(acc: acc, f: f))
      }
  }

  func foldl<R>(acc: R, f: (R,T) -> R) -> R {
      switch self {
      case .`nil`:
          return acc
      case .cons(let head, let tail):
          return tail.foldl(acc: f(acc, head), f: f)
      }
  }

  func map<R>(f: @escaping (T) -> R) -> List<R> {
      return foldr(acc: List<R>.`nil`) { acc, list in
          return List<R>.cons(f(acc), list)
      }
  }

  func filter(f: @escaping (T) -> Bool) -> List<T> {
      return foldr(acc: List<T>.`nil`, f: { acc, list in
          if f(acc) {
              return List<T>.cons(acc, list)
          }
          return list
      })
  }

  func flatMap<R>(f: @escaping (T) -> List<R>) -> List<R> {
      return foldr(acc: List<R>.`nil`, f: { acc, list in
          return f(acc).append(list: list)
      })
  }

}

let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`)))
list.map { $0 * 10 } // "10,20,30,nil
list.filter { $0 % 2 == 0 } // 2,nil
list.flatMap { .cons($0, .cons($0, .`nil`)) } // 1,1,2,2,3,3,nil

特定文字をリンクするTextView

特定の文字をリンクにしたいときに使うTextView

LinkableTextView.kt

class LinkableTextView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null
) : TextView(context, attrs) {

    private var target = ""
    private var color = 0
    private var linkBold = false
    var listener: Listener? = null

    init {
        val a = context.theme.obtainStyledAttributes(
                attrs,
                R.styleable.LinkableTextView,
                0, 0)

        try {
            target = a.getString(R.styleable.LinkableTextView_target)
            color = a.getColor(R.styleable.LinkableTextView_link_color, 0)
            linkBold  = a.getBoolean(R.styleable.LinkableTextView_link_bold, false)
        } finally {
            a.recycle()
        }
    }

    override fun setText(text: CharSequence?, type: BufferType?) {
        if (TextUtils.isEmpty(target)) {
            super.setText(text, type)
            return
        }
        val p1 = Pattern.compile(target)
        val m1 = p1.matcher(text)
        val ss = SpannableString(text)

        if (m1.find()) {
            val start = m1.start()
            val end = m1.end()
            movementMethod = LinkMovementMethod.getInstance()
            ss.setSpan(object : ClickableSpan() {
                override fun onClick(widget: View) {
                    listener?.onLinkClick()
                }
            }, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
            if (color != 0) {
                ss.setSpan(ForegroundColorSpan(color), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
            }
            if (linkBold) {
                ss.setSpan(StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
            }
        }
        super.setText(ss, type)
    }

    interface Listener {
        fun onLinkClick()
    }

}

attr.xmlから要素を取得しその値に応じてリンクさせたい文字列や見た目を設定していきます。 targetはリンクさせたい文字列、colorはリンクさせたい文字列の色、linkBoldリンクさせたい文字列がBoldかどうか を指定します。

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="LinkableTextView">
        <attr name="link_color" format="color"/>
        <attr name="target" format="string"/>
        <attr name="link_bold" format="boolean"/>
    </declare-styleable>
</resources> 

上記した3つの要素を指定しています。

使い方

xmlで置くときに3つの要素に値を設定します。

     <LinkableTextView
            android:id="@+id/id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:text="text"
            android:textColor="@color/your_color"
            app:target="target"
            app:link_color="@color/your_color"
            />

コードでリンクをクリックした際の動作を書きます。

   binding.linkableTextView.text = "text"
   binding.linkableTextViewlistener = object: LinkableTextView.Listener {
             override fun onLinkClick() {
                //do something
             }
    }

僕の行動指針

行動指針 Advent Calendar 2016 - Adventar17日目の記事です。

行動指針を書くとなるとなかなか難しいですね。 ということで紹介していきます。

行動指針

自分の常識を疑う

世の中いろんな人がいるので自分の常識と違う人はたくさんいます。そういった人に時には怒りや苛立ちを覚えることもあると思います。そんな時は自分の常識はあくまで自分だけの常識であり、間違っているのは自分ではないかとまず意識するようにしています。常識なんて人の数だけあるので、もはや常識とは?くらいの気持ちです。

反対意見こそ感謝

反対意見は賛成意見より言うのが難しいと思います。人の考えを否定するわけですから簡単には言えないですよね。じゃあなんで反対意見を言うのかというと物事を良い方向に進めるためです。そのために自分の考えに反対してくれるってありがたいと思います。後輩からの反対意見は特に感謝だと思います。先輩に「そうっすね」って言っておけば良いのにわざわざ反対するわけです。自分が先輩のときはそうゆう心構えを持つようにしています。

セルフ・ハンディキャッピングに気をつける

セルフ・ハンディキャッピング - Wikipediaという言葉があります。試験前に「俺勉強してないわ〜」というような「自分○○なんでこれはできないんですよ」と先に逃げ道を作っておくアレのことです。これをやっても何も良いことはありません。苦手なことや難しいことに直面したときは言い訳して逃げ道を作るのではなく、何も言わずにただやれば良いだけです。

おたくになろう

おたくになるほど好きなものがあるって素晴らしいですよね。刹那的な生き方もあると思いますが、好きなものに継続的に情熱を捧げていく方が良いのかなと思っています。

以上です。行動していこう。

Swiftのmapのその次へ

少し前に社内勉強会で発表する機会があったので Swiftのmapからその次へというタイトルでスライドを作りました。

speakerdeck.com

一言で言えばswiftのOptionalやArrayはFunctorだよという話です。 swiftはマルチパラダイム言語と言われており、関数型言語の特徴を取り入れています。 その中の概念の一つにFunctorというものがあり、これをmapを使っている人に知ってほしいその次(の概念)と本発表では呼んでいます。 次はApplicative functorやMonadの発表をしたいのでこのあたりを勉強していきます。