書いて覚えるSwift入門

第27回 静かなること型の如し

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

型の完全理解は可能か?

今回はSwift最大の特長であるプロトコル(protocol)を,総称型(generic type)と絡めつつ紹介します。そのためには,(type)とは何かをまず理解しておく必要があります。型とは何か,なんとも深淵そうな質問で,実際それだけでTAPLこと型システム入門という名著がまるごと1冊書けてしまうほどなのですが,本連載は「書いて覚えるSwift入門」⁠実際に書いていくことにしましょう。

0 == 0.0 // compile error

REPLで次のとおりに入力してみましょう。

var i = 0
i == 0
i == 0.0

macOSでは次のようになります。

Welcome to Apple Swift version 3.1
(swiftlang-802.0.53 clang-802.0.42). Type
:help for assistance.
  1> var i = 0
i: Int = 0
  2> i == 0
$R0: Bool = true
  3> i == 0.0
error: repl.swift:3:3: error: binary operator'==' cannot be applied to operands of type 'Int' and 'Double'
i == 0.0

repl.swift:3:3: note: expected an argument
list of type '(Int, Int)'
i == 0.0

(Objective)?C(++)?やJavaなどのコンパイル言語に慣れた人にとっては当たり前のこの挙動は,JavaScriptやPerlやPythonやRubyなどのスクリプト言語にとっては驚きの結果です。

node(JavaScript)

> var i = 0
undefined
> i == 0
true
> i == 0.0
true

perl -de 1

main::(-e:1): 1
  DB<1> my $i = 0

  DB<2> p $i == 0
1
  DB<3> p $i == 0.0
1

python

>>> i = 0
>>> i == 0
True
>>> id == 0.0
True

irb(ruby)

irb(main):001:0> i = 0
=> 0
irb(main):002:0> i == 0
=> true
irb(main):003:0> i == 0.0
=> true

スクリプト言語で0 == 0.0が成立する理由は厳密にはそれぞれの言語で異なるのですが,Swiftで0 == 0.0が成立しない理由は明白です。型が一致しないからです。0Intという型で,0.0Doubleという型になります。そしてSwiftの型は静的。コンパイルの段階でどの変数(および定数)がどんな型なのかがあらかじめ決まっているので,0 == 0.0は実行すらさせてくれないというわけです。

なぜSwiftでは00.0は別々の型なのでしょう?

別の役割が期待されているからです。

たとえば割り算。IntDoubleでそれぞれ/してみましょう。

  1> var i = 42
i: Int = 42
  2> i / 10
$R0: Int = 4
  3> var d = 42.0
d: Double = 42
  4> d / 10
$R1: Double = 4.2000000000000002

かたや4かたや4.2000000000000002。何が違うか。そう,余りです。

  5> i % 10
$R2: Int = 2
  6> d % 10
error: repl.swift:6:3: error: '%' is
unavailable: Use truncatingRemainder insteadd % 10

Swift.%:2:13: note: '%' has been explicitlymarked unavailable here
public func %(lhs: Double, rhs: Double) -> Double

整数の範囲で「割り切る」代わりに「余り」%で出せるのがInt/で,精度一杯まで「割り続ける」代わりに「余り」を出さないのがDoubleの/。こういった区別がない言語では,==が楽な代わりにほかで苦労しています。たとえばJavaScriptにはDoubleに相当するNumberはあってもIntに相当する型はないので,Swiftの42 / 10に相当する演算は(42 / 10) ¦ 0などとしなければなりません。

引数をそのまま返すだけの簡単なお仕事

ここで,1番目に簡単な関数を考えてみましょう。ちなみに0番目に簡単な関数は何も引数を取らず何もしない関数で,Swiftならばこうなります。

func noop(){}

これが0番目なら,1番目は当然1つ引数をとってそれをそのまま返す関数になるでしょう。簡単ですね――動的言語なら。

JavaScript

function id(x){ return x }
// es6 ならもっと簡単に var id = (x)=>x;

Perl

sub id { @_ }

Python

def id(x):
  return x

Ruby

def id(x)
  x
end

それではSwiftでは? Swiftは静的型言語(大事なことなので何度も繰り返します)⁠関数を定義するときには,引数と戻り値を明示しなければなりません。ということは……

func id(_ x:Int)->Int { return x }
func id(_ x:Double)->Double { return x }
func id(_ x:String)->String { return x }
// ...

こういうのを繰り返し書かなければならないということでしょうか? やってることどころか{}の中身もまったく同じなのに?

ここで颯爽と登場するのが総称型(generictype)。次のように書いておけば……

func id<T>(_ x:T)->T {
    return x
}

何でもござれです。

  1> func id<T>(_ x:T)->T {
  2. return x
  3. }
  4> id(0)
$R0: Int = 0
  5> id(0.0)
$R1: Double = 0
  6> id("")
$R2: String = ""
  7> id([0])
$R3: [Int] = 1 value {
  [0] = 0
}
  8> id([0:""])
$R4: [Int : String] = 1 key/value pair {
  [0] = {
    key = 0
    value = ""
  }
}

ここでidは総称関数(generic function)Tプレイスホルダー型(placeholder type)と言います。

著者プロフィール

小飼弾(こがいだん)

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

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

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

コメント

コメントの記入