- 投稿日:2020-02-11T18:23:56+09:00
Swiftで高階関数(Higher Order Function)を活用してスッキリ&カッコ良いコードを書く
はじめに
Swiftでプログラミングをしていても、UI周り部分の実装がメインとなり、GenericsやHigher Order Functionをあまり活用できていないように思います。
他の言語扱う中でも、必ずと言っていいほど、GenericsやHigher Order Functionは出てきますし、どうせなら、カッコ良いコード書きたいので、
Swift言語を使って復習していきます。名前リストを挨拶リストに変換
let nameList: [String] = ["Yamada", "Tanaka", "Nakano"]名前リストから、
敬称
と挨拶
をつけたリストを作り出すには、map
を使ってシンプルに書くと、let morningMrList = nameList.map { "Mr. \($0) Good morning!" }のようになります。
map
を使っているので、十分スッキリ書けていると思います。ただ、敬称を
Ms.
に変更したい、 挨拶を、Good evening!
に変更したい場合にlet morningMsList = nameList.map { "Ms. \($0) Good morning!" } let eveningMrList = nameList.map { "Mr. \($0) Good evening!" }ベタ書きため再利用性がない事に気づきます。
この問題を解決するために、最初に考えられるのは、map処理の中で
敬称
と挨拶
を分離する。let morningMrList2 = nameList.map { "Mr. \($0)" }.map {"\($0) Good morning!"}処理が別れたので再利用性が高くなりそうです。
次に、名前リスト毎に文字列加工処理(敬称+挨拶)をmapでベタ書きするのは再利用できないので、
名前リストと文字列加工処理をパラメータとして受け取る関数を用意します。関数をパラメータとして受け取る高階関数となります。
func _map<String>(_ list: [String], f: (String) -> String) -> [String] { return list.map { f($0) } } // 名前リストを挨拶リストに変換 func _mrMorningF (_ list: [String]) -> [String] { return _map(list) { "Mr. \($0) Good morning!" } } let morningMrList3 = _mrMorningF(nameList)ただ、このコードはベタ書きであり、スッキリしません。
以下では戻り値が関数の高階関数を使って改善していきます。
戻り値が関数の高階関数を使って改善
- 敬称、挨拶の文字列追加を分離
- 文字列加工の関数化
この2つが実現出来れば、名前リストから挨拶リストを生成するのに、毎回ベタ書きしなくてよくなります。
上のmapでは、戻りの型が配列でしたが、戻り値を関数に書き換えます。戻り値の型に束縛されないのでGenericsで書くことができます。
func map<A, B>(_ f: @escaping (A) -> B) -> ([A]) -> [B] { return { $0.map(f) } }戻り値は、引数に任意の型の配列(A)を受け取り、任意の型の配列(B)を返す関数となります。
Genericsを使っているので、汎用的な関数を用意出来たと思います。アロー(
->
) が連続すると、とたんに可読性が落ちますが、代わり玄人感はすごく増しますね。このmapを使うと以下のような、敬称文字列追加、挨拶文字列追加を定義できます。
// 名前に、敬称のMrを追加する関数 let mrF: ([String]) -> [String] = map{ "Mr. \($0)" } // 名前に、挨拶を追加する関数 let morningF: ([String]) -> [String] = map{ "\($0) Good morning!" }nameList, nameList2のように別のリストでも、引数を変えるだけで済みます。
let morningMrList4 = morningF(mrF(nameList)) let morningMrList5 = morningF(mrF(name2List))他の挨拶の関数も同様に書けます。
let msF: ([String]) -> [String] = map{ "Ms. \($0)" } let afternoonF: ([String]) -> [String] = map{ "\($0) Good afternoon!" } let eveningF: ([String]) -> [String] = map{ "\($0) Good evening!" }これらを組み合わせれば名前から挨拶リストを作り出せるので、再利用可能です。
関数ネストを改善したい
関数がネストしているので少し見にくい気がします。
morningF(mrF(nameList))これを解決するには、関数を合成する関数を用意すれば良いでしょう。本格的な高階関数ですね。
2つの関数を引数を受け取り、これらの関数を合成して関数を返却しています。
func compose<A, B, C>(_ f: @escaping((A) -> B), _ g: @escaping((B) -> C)) -> (A) -> C { return { a in g(f(a)) } }型に縛られない関数なので、Genericsで書くことが出来ます。
これを使うと、
let msMorningF = compose(msF, morningF)使う側も関数がネストしていないので、スッキリ読みやすいです!
let morningMsList2 = msMorningF(name2List)まとめ
毎回、同じ処理を書く場合は、高階関数(特に、戻り値を関数)にすることを検討すると再利用性が高いコードにすることが
出来るケースがあることが理解できました。Swiftの高階関数の記事は、
map
flatMap
reduce
などの使い方を説明に閉じた記事が多いですが、
自身で高階関数を用意して使うことが本当の活用な気がします。そして、Generics +高階関数の組み合わせは非常に強力ですね。
この記事にあたり、Point Free で勉強させてもらいましたが、海外サイトは良質な記事が多いので、英語頑張りたいです。
- 投稿日:2020-02-11T17:02:12+09:00
Swiftの記号あれこれ
背景
Swiftの公式ドキュメント、ヘルプガイド、ブログのコードサンプルなどによく出てくる実装のなかで、どんな意味があるのかすぐに判断できない記述がありました。
メソッドの引数が顔文字化していたり(例えば、max(:_:_)
とか)、変数定義の型の最後にに?
や!
が付いていたりと、あれ?これはどういう意味だったかなというときに自分自身がすぐに思い出すとき用の記事があるといいなぁと思い記事にしました。対象とする読者
Swift初心者
環境
私がこの記事を書いている際に利用しているのは次の環境です。
Xcode 11.3.1
Swift 5.0Swiftの記号あれこれ
関数の引数に出現する_ (アンダースコア)
Swiftでは関数の引数にラベルをあらかじめ付けておき、利用者にそれを明確に指定させることができるが、名前をわざわざ指定しなくても明らかにわかる場合は、その仕組みだとコードが読みにくくなってしまいます。
なので、関数呼び出しするときの引数が、名前なしで渡せますよということを明示するもの。// 引数に名前付きで呼び出してもらう関数の書き方 func add(x: Int, y: Int) -> Int { return x + y } var r1 = add(x: 3, y: 2) // 5: OK var r2 = add(3, 2) // NG(引数に名前がないのでコンパイルエラー) // 引数に名前なしで呼び出してもらう関数の書き方 func addByNoLabel(_ x: Int, _ y: Int) -> Int { return x + y } var r3 = add(4, 7) // 11: 引数に名前なしでOK関数のシグネチャーで、
func(_:)
とかfunc(_:_:)
とかの表現を見かけたときは、_:
のところに、名前なしで引数を入れれば利用できるんだな、ぐらいで見ておけばよいです。型のうしろに?(はてな)
?
通称:はてなマーク
正式名:クエスチョンマーク値がオプショナル型であることを示す場合に利用します。オプショナル型は、指定した型の値もしくは値そのものがない
nil
のいずれかを表す型です。
Optional<Wrapped>
(WrappedにはInt,Stringなど実際の型が入る)が入りますが、コードが冗長となるのを避けるためのシュガーシンタックスとして用意されています。// 正式な構文だと var age: Optional<Int> // 簡潔に書くと var age: Int? = 21 // 利用時には、アンラップが必要(強制アンラップした場合) var nextAge :Int = age! + 1 // 22: オプショナルの値に!を付けて強制アンラップ変数等の後ろに
?
が出現した場合は、この変数は空っぽの状態(nil)もあり得るということと、Optionalを解除(Wrap包み紙から出してあげるイメージ)しないと、元の型としては利用できないという認識を持っておけばよいです。型のうしろに!(びっくり)
!
通称:びっくりマーク
正式名:エクスクラメーションマークこちらもオプショナル型ですが、
?
とは異なり!
で定義した値にアクセスする際には自動的に強制アンラップして元の型にしてくれます。なので「暗黙的にアンラップされたOptional<Wrapped>
型」と呼ばれています。
型としては、Optional<Wrapped>
型だけど、利用するときには通常のWrapped
型として利用可能。
注意点としては、値にアクセスしたときにnil
だった場合は、実行時エラーとなってしまいます。var age: Int! = 21 //アクセス時に自動的に強制アンラップしてくれる var nextAge: Int = age + 1 // 22範囲演算子(終了を含まない)
a..<b
aから始まり、終了bを含まない範囲。var range 1..<4 // CountableRange(1..<4) for value in range { print(value) } 実行結果 1 2 3範囲演算子(終了を含む)
a...b
aから始まり、終了bを含む範囲。var range 1...4 // CountableClosedRange(1...4) for value in range { print(value) } 実行結果 1 2 3 4さいごに
慣れない言語でのコーディングは不安です。
でも、知ってしまえば、慣れてしまえばそれほど難しくないことは多々あります。
私も今回のこの記事を自分で整理してアウトプットすることで、頭の中を整理できました。
私のようなSwift初心者の方に、この記事が少しでも役立てば幸いです。
- 投稿日:2020-02-11T14:35:47+09:00
【Swift】SequenceとIteratorProtocolでfor-inですべてのデータを取り出すサンプル
SequenceとIteratorProtocolを使ってfor-inですべてのデータを取り出す書き方のサンプルです。
SequenceとIteratorProtocolのサンプルclass T { var n = 1; init(_ n: Int) { self.n = n } } class C: Sequence, IteratorProtocol { typealias Element = T var ta = [T](repeating: T(1), count: 100) var i = 0 func next() -> T? { guard i < ta.count else { i = 0 return nil } let r = ta[i] i += 1 return r } } var c = C() for e in c { print(e.n) }
- 投稿日:2020-02-11T11:47:35+09:00
Swifterを使って画像を投稿する
はじめに
SwiftでTwitterAPIを使って画像を投稿したかったのですがあまり記事がなかったので投稿します。
Swifterというライブラリを使っています。ドキュメントには画像を投稿することについては書いていなかったのですがコード読んでたらそれらしきものがあったのでそれを使いました。
https://github.com/mattdonnelly/Swifter今回やること
- SwifterでaAuth認証する。
- 画像をツイッターに投稿する。
OAuth認証する。
これに関しては記事を別に書いているのでそちらを見てください。
https://qiita.com/daichi77/items/88fcffaf11585033fdd4
画像を投稿する。
let image = UIImage(hogehoge)//投稿する画像を入れる。 let data = image.pngData()//data型に変換 //aouth認証で入手したやつを入れる。 let swifter = Swifter(consumerKey: 取得したkey, consumerSecret: 取得したkey, oauthToken: 取得したkey, oauthTokenSecret: 取得したkey) //ツイート。statusには文をmediaには画像をdata型に変換したものを入れる swifter.postTweet(status: "test", media: data!,success: { json in print(json)//成功時 }, failure: {error in print(error)//失敗時 })こんな感じでツイートできました。
文章のみ投稿するpostTweetはドキュメントに載っていましたが画像投稿も同じ感じでツイートできました。
補足(413 payload too large errorが帰ってくる場合 )
自分の場合iPhoneのフォルダーから画像を読み取ってそれをアプリに保存した後に
再度アプリから保存した画像を読み取りツイッターに投稿という手順だったのでこの手順のどこかで画像が大きくなってしまったのかもしれません。
もしかしたら同じエラーが帰ってくる人がいるかもしれないので解決方法を載せておきます。let image = UIImage(hogehoge)//投稿する画像 //大きさをresizeする let width = image.size.width * 0.1 let height = image.size.height * 0.1 UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 1.0) image.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) let resizeImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() //大きさをresizeした後にdata型に変換 image.pngData()こんな感じで画像の大きさをresizeしてあげるとうまくtweetできました。0.1倍じゃなくていいかもしれませんがいい感じの大きさにしてツイートしてあげてください。
- 投稿日:2020-02-11T10:48:45+09:00
SwiftでAtCoder攻略するノウハウを色々書いていく記事
半年ぐらい離れていたんですが、最近またAtCoderをやっています。
Swift使いとしては、AtCoderのSwift2縛りがキツいんですが、ぼちぼちコンパイラアップデートしてくれそうです。【言語アップデート】新ジャッジのテスト環境を公開しました。ほぼテストできていない言語も数多くあるので、ぜひ各言語で確認提出をしていただければと思います。ご協力よろしくお願いします。https://t.co/IFWMzsQbv5
— AtCoder (@atcoder) January 31, 2020競プロ真剣にやるんなら、C++が圧倒的有利なんですが、
今の僕の状況的にC++まで手出せないので、Swiftで戦います。この前気付いたんですが、AtCoderにSwiftで参戦している人口はめちゃくちゃ少ないです。
この記事はアップデートしていく自分用ノートみたいにしたいかなと思っています。
Swift5->swift2への逆引き(?)みたいな記事ってないので、案外困るんですよね。。。やっていく中で詰まったら順次追記していきます。
後AtCoder側がSwift5にアップデートされたらこの記事自体要らなくなると思います。テンプレート
swift2です。
標準入力// 1 let N = Int(readLine()!)! // 1 2 let split = readLine()!.componentsSeparatedByString(" ") let N = Int(split[0])! let S = Int(split[1])! // 1 2 3 4 5 let array = readLine()!.componentsSeparatedByString(" ").map { Int($0)! }便利extensionextension Array where Element: Equatable { mutating func remove(value: Element) { if let i = self.indexOf(value) { self.removeAtIndex(i) } } }パクリ元:swiftで配列から値を指定して削除する
sortvar array = [1, 2, 3, 4, 5] array = array.sort { $0 > $1 } // 降順文字列のcountlet S = "aaaaa" print(S.characters.count) //5配列の最大値/最小値array.maxElement()! array.minElement()!エディタ問題
playgroundだと標準入力が受け取れなくて、
readline部分を書き換えて、変数で受け取っちゃうとか、やりようはあるんですが、まあちょっとめんどくさいですよね。最近気付いたんですが、コマンドラインツールとしてプロジェクト作成すれば、
標準入力を受け取るプログラムとしてSwiftが書けて、入力補完が効くので嬉しいので、これでやるのが一番いいですね。
AtCoder側がSwift5に対応してくれた後なら。一昨日これで参戦したら、やっぱいちいちSwift2向けに書き換えるオーバーヘッドがしんどくて、
結局いつの間にかAtCoder上のコードテストでデバッグしている僕がいました。現状ブラウザで書いちゃうのが一番いいかなあという感じです。
関連記事
- 投稿日:2020-02-11T08:08:20+09:00
SwiftUIのTextのfontとfontWeight一覧
概要
文字の大きさや太さを一覧で視覚的に残しておきたかったので書きました。
当たり前っちゃ当たり前の内容ですが、パッと見比べる時に有用であればいいなと思います。font
文字の大きさを指定します。
ドキュメントなど参照しましたが、デフォルト値を見つけることができなかったのですが、
見た感じは.body
かと思います。Text("largeTitle").font(.largeTitle) Text("title").font(.title) Text("headline").font(.headline) Text("subheadline").font(.subheadline) Text("body").font(.body) Text("callout").font(.callout) Text("caption").font(.caption) Text("footnote").font(.footnote)fontWeight
文字の太さを指定します。
こちらもデフォルトは見つからなかったですが、見た感じは``。Text("black").fontWeight(.black) Text("bold").fontWeight(.bold) Text("heavy").fontWeight(.heavy) Text("light").fontWeight(.light) Text("medium").fontWeight(.medium) Text("regular").fontWeight(.regular) Text("semibold").fontWeight(.semibold) Text("thin").fontWeight(.thin) Text("ultraLight").fontWeight(.ultraLight)組み合わせ
組み合わせるとこのような感じに。
Text("largeTitle & black").font(.largeTitle).fontWeight(.black) Text("footnote & ultraLight").font(.footnote).fontWeight(.ultraLight)