Edited at

型なし言語のメリットを一言で書いてみる

処理系に関する特性をいったんおいておけば、一言でいうと

「型を付けるのがめんどくさい or 型を付けるのが不可能」が正しいプログラムを簡潔に表現できる

ことが大きいと私は思います。

これだけですと、色々飛ばしすぎなので、より具体的な例として、JSONを使って説明してみます。JSONがなにかについては記事を読む人は概ねわかると思いますので、解説を省略しますが、素朴にJSONの値に対して型を与えると、

Scalaの場合:

sealed abstract class JsonValue

case class JsonObject(val members: Map[String, JsonValue]) extends JsonValue
case class JsonNumber(val value: Int) extends JsonValue
case class JsonString(val value: String) extends JsonValue
...

Kotlinの場合:

sealed class JsonValue {

data class JsonObject(val members: Map<String, JsonValue>) : JsonValue()
data class JsonNumber(val value: Int) : JsonValue()
data class JsonString(val value: String) : JsonValue()
...
}

Javaの場合:

import java.util.*;

public class Json {
public static abstract class JsonValue {}
public static class JsonObject extends JsonValue {
public final Map<String,JsonValue> members;
public JsonObject(Map<String, JsonValue> members) {
this.members = members;
}
}
public static class JsonNumber extends JsonValue {
public final int value;
public JsonNumber(int value) {
this.value = value;
}
}
public static class JsonString extends JsonValue {
public final String value;
public JsonString(String value) {
this.value = value;
}
}
...
}

Haskellの場合:

import qualified Data.Map as M

data JsonValue = JsonObject { members:: M.Map String JsonValue }
| JsonNumber Int
| ...
| JsonString String deriving Show

といった感じになるかと思います。ここで、


  • 実用的にはクラスとJSONをマッピングするライブラリとか使うのでは

  • 実用的にはこのような表現をしないだろう(他のエンコードをするだろう)

といった疑問があるかと思いますが、今考えているのはJSON一般に対して、素朴にどういう型を与えるかという話なので、いったん脇に置いておきます。

このようにして、とりあえず任意のJSONの値に対する型を与えることはできました。しかし、このような素朴な表現に問題があるということはすぐ理解できるかと思います。

たとえば、以下のようなJSONを取り扱うことを考えてみます。

{ "name" : "Taro", "age": 30 }

ただし、前提条件として、取り扱うJSONは必ず


  • 属性nameを持ち、その値は文字列である

  • 属性ageを持ち、その値は数値である

  • それ以外の属性はない

であることがあらかじめわかっているものとします。

Scalaなら

val person: JsonObject = ...

val name: String = person.members("name").asInstanceOf[JsonString].value
val age: Int = person.members("age").asInstanceOf[JsonNumber].value
println(name)
println(age + 1)
...

といった形になります。ここで、asInstanceOfでキャストしているのは、前提条件が満たされているのならば、パターンマッチで場合分けすることが無意味だからです。

さて、最初、JSON全体に対して型を考えたわけですが、実際には特定の形式のJSONを相手にするといったことは珍しいことではないかと思います(上の話も(単純化しましたが)そのケースです)。このような場合において、特定の形式のJSONに対してより詳細な型を与えられると便利ですし、無駄なキャストによって「抜け穴」が生じることを防ぐことができます。

Scalaだと、circeなどのライブラリを使うと、case class <-> JSONのマッピングを半自動的に生成してくれたりしますし、他にも色々、具体的なJSONを簡単に取り扱うためのマッピングを行うライブラリは存在します。Javaでも事情は同様で、class <-> JSONのマッピングを半自動的に生成するライブラリは、jacksonをはじめとしていくつもあります。Haskellについては詳しくありませんが、おそらく似たような技術はあるかと想像します。

それらのライブラリを使うことで、特定のJSONを扱うときにそれに対応する型を与えることが簡単になります。それぞれに使われている技術の詳細についてはいったんおいておきます。

しかし、本来やりたいことを考えると、

println(person.name)

println(person.age + 1)

と表現したいわけであって、何故余分な作業をしなければいけないのかという疑問が湧きます。そのような余分な作業をしなくていいように、型システムを拡張するというアプローチを考えることができます。ただし、JSON一般に対して型を与えるなら、

person.age + 1

というプログラムに型が付いてしまうような感じに素朴に型システムを拡張してしまうと、おそらく健全性がぶっ壊れてしまい、逆にまずいです。実際にはScalaであればType Dynamicを使うことで、型システム全体を破壊することなくなんとかすることができますが(これは部分的に型検査を無効にする機構を入れたものと見ることができるでしょう)。

このようなケースにおいて、型なし言語なら、素朴に

person.age + 1

と表現できることが多いでしょう。ここで、特定の具象構文を問題にしているわけではないことに注意してください。

(+ (get-age person) 1)

であっても、本質的には変わりません。型なし言語において、何故このような表現が可能かというと、そもそも型がないから型検査を通らないという問題が起きないためです(安全であるという性質をすっ飛ばしていますが、それはおいといて)。

JSON以外でも、このような、型付けの問題がめんどくさいケースというのはしばしばあると思います。RailsのActiveRecordのような何かについて、型を考えるとか。現実的には、頑張れば無理とは言えないにせよ、めんどくさいことには変わりないように思います。

そういった問題を扱うときに型なし言語はしばしば便利である、というのが私の感想です。当然、これにはトレードオフがありますが、それについてはさんざん議論がなされているので説明は不要でしょう。

ざざっと書いたので、用語の使い方や論理構成がおかしくなっている可能性があるかもしれません。その辺は遠慮なくご指摘いただければと思います。