トラック
/
Julia
Julia
/
演習
/
採点の達人
採点の達人

採点の達人

学習演習

はじめに

これまで、シラバスでは型についてあまり触れてきませんでしたが、Juliaには確かに型があります。

julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}

型を指定したことはありませんが、Juliaはそれでも型を割り当てます。

  1. Juliaにはtypesがあり、これは設計の中心となっています。
  2. Juliaはたいてい、Type Inferenceを使って型を「推測」できます。

JITコンパイラーは(すべての)コードに目を通し、変数がどのように使われているかを見て、その使われ方に合った適切な既定の型を_推論_します。

型の割り当て

簡単なチュートリアルの演習を解くだけなら、型推論に頼っても問題ありません。しかし、より大きなプログラムでは、もっと精密な制御が必要になることが多いでしょう。

最近のほとんどのプロセッサーでは、整数は既定でInt64になります。 Numbersのコンセプトで、値を既定ではない特定の型に変換できることを見ました。

julia> x = Int16(42)
42
julia> typeof(x)
Int16

しかし、変数xには、別の型を再代入できます。

julia> x = "changed"
"changed"
julia> typeof(x)
String

これがtype instabilityであり、次のようなものです。

  • 小さなスクリプトではとても便利です。
  • 大きなプログラムでは、性能と信頼性の面で悪影響があります。

代わりに、::演算子を使ってxの型を設定できます。

julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.

これでyはInt16型であり、これからもずっとInt16型のままです。 そのため、コンパイラーはyのために何バイト予約すればよいかを把握でき、残りのコードも安定した型を前提に最適化できます。 この点で、この変数はCのような静的型付け言語の変数とほぼ同じです。

型の階層

Int64、Int16、String、Char。これらの型は、いったいどこから_「来る」_のでしょうか。

多くのオブジェクト指向(OO)言語では、各型はクラスであり、サブクラス化によってクラス階層に整理され、クラスメソッドが振る舞いを定義します。

JavaとRubyはこのパターンの明らかな例ですが、Pythonも内部的には似ています。

Juliaにはクラスがありません。

文書化された理由は、OOの機能がJITコンパイラーを妨げ、実行時の性能を損なうからです。

それでも、次のコードを見てください。

julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)

いくつか詳細を補足します。

  • Int16は型であり、この型の変数を作成できます。
  • Int16はSignedのサブタイプであり、supertype()関数がそれを示してくれます。
  • 型の階層があり、Integer、Real、Numberを経て最上位のAnyまで続いています。subtypes()はこの階層の枝を一覧表示してくれます。

すべての枝はAnyで終わり、Anyは自分自身が自分のスーパータイプであるという点で特別です。

julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any

つまり、Juliaにはclassの階層はありませんが、typeの階層は_確かに_あります。

いずれ、これがどのように機能するのかを解き明かしていきますが、その前に探求すべきことがたくさんあります。

型のテスト

typeof()を使って、通常どおり等しいかどうかをテストできます。

julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false

型の等価性は厳密でなければなりません。この形の比較は型の階層をまったく理解しないからです。

より柔軟に、isaは、値が比較対象と同じ型か、そのサブタイプかどうかを教えてくれます。 関数の形でも中置の形でも使えます。

julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false

isaは左側にvalueを期待し、typeではないことに注意してください。

この方法で2つの_型_を比較しようとすると、予期しない結果になります。 正しい演算子は<:で、今後のコンセプトで何度も登場します。

julia> Int64 isa Number  ## Don't do this!
false
julia> Int64 <: Number
true

抽象型と具象型

型の階層が木構造をなしていることを見ました(計算機科学の意味での木で、根が一番上にあります)。

木の各項目はnodeであり、これらはいくつかのカテゴリーに分けられます。

  1. サブタイプを持つノードはabstractと呼ばれます。
  2. サブタイプを持たない葉ノードはconcreteと呼ばれます。
julia> subtypes(Integer)  # an abstract type
3-element Vector{Any}:
 Bool
 Signed
 Unsigned
julia> subtypes(Int64)  # a concrete type
Type[]

これは重要な区別です。変数としてinstantiatedできるのは具象型だけだからです。

julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64

抽象型を使おうとしても(この場合)エラーメッセージは出ませんが、コンパイラーが適切な具象型、つまりIntegerではなくInt64を作成することに注意してください。

説明

この演習では、成績データを前処理するという、ちょっとしたデータエンジニアリングに取り組みます。とてつもなく多く(多すぎる?)の生徒を抱える学校が、生徒が受け取った成績を分析しようとしていて、その作業を効率よく進めたいと考えています。そこで、データをできるだけ小さくまとめて整理するのが狙いです。

成績の尺度は通常0〜10なので、成績1つにつき8ビットしか使わないデータ型を使うのがもっとも効率的です。

  • 教授の中には実数を使う人もいます(正確さにこだわる人だからです)。その場合は、それをUInt8データ型に変換する必要があります。
  • また別の教授は負の数を含む整数を使います(とくに言うことを聞かない生徒を罰するためです)。こうした場合は、それをInt8データ型に変換する必要があります。

成績を変換する関数を実装できたら、次に、その成績を収めているコレクションを扱う関数を書きます。成績を降格し、降順に並べ替えて返すようにします。

  • 教授の中には、データに几帳面なため、成績を昇順に並べたVectorで保存する人もいます。
  • また別の教授は、少しばかりずぼらなので、順序を持たないSetに成績を保存しています。

どちらの関数でも、不正な入力はMethodErrorをスローして処理する必要があります。

Note

例外処理は後のコンセプトで扱いますので、この演習では次の構文を使えば大丈夫です。

throw(MethodError(f, args))

ここでfは関数、argsはその関数に入力される引数のタプルです。 demote(n)関数の場合、fはdemote、argsは(n,)です。

throw(MethodError(demote, (n,)))

1. 成績を降格する

demote(n)メソッドを実装します。

  • Float64の入力に対しては、最も近い整数に切り上げ、UInt8データ型で返します。
  • 任意のIntegerに対しては、同じ整数をInt8データ型で返します。
  • それ以外の入力はすべてMethodErrorをスローします。
julia> demote(4.2)::UInt8
5

julia> demote(4)::Int8
4

julia> demote("hi")
MethodError: no method matching demote(::String)     #output truncated

2. データを前処理する

preprocess(coll)メソッドを実装します。

  • Vectorの入力に対しては、すべての数値を降格し、ベクターを逆順にします。
  • Setの入力に対しては、すべての数値を降格し、降順に並べ替えたベクターを返します。
  • それ以外の入力はすべてMethodErrorをスローします。
julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
 3
 2
 1

julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
 6
 4
 3

julia> preprocess(42)
MethodError: no method matching preprocess(::Int64)     #output truncated
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Julia Exercism

採点の達人を始める準備はできましたか?

Exercismに登録すれば、35個のコンセプト128個の演習、そして本物の人間によるメンタリングとともに、Juliaを学んでマスターできます。すべて無料です。