Skip to content

Latest commit

 

History

History
518 lines (420 loc) · 20.5 KB

Types.md

File metadata and controls

518 lines (420 loc) · 20.5 KB

型システムは以下の型を定義します:

  • 原始型: Int, Number, String, Char, Boolean
  • 配列
  • レコード
  • 代数的データ型(タグ付き共用体)
  • ユーザ定義型
  • 関数
  • 多相型
  • 制約のある型
  • 型シノニム(型同義語)

原始型

StringNumberBooleanの各原始型は、JavaScriptのそれと対応しています。

整数型

Int型は整数値を表現します。ランタイムではJavaScriptの普通の数字と同じですが、Int値を受け取る演算子(+)などは、常にInt値を返すように、異なる定義になっています。

配列

PureScriptの配列はランタイムでJavaScriptの配列と対応していますが、全要素が同じ型でなければなりません。Array型は、引数を1つ取ることで、自分自身の型が何であるかを特定します。例えば、整数の配列は型Array Intを持ち、文字列の配列は型Array Stringを持ちます。

レコード

PureScriptのレコードはJavaScriptのオブジェクトに対応しています。これは0個以上の名前付きフィールドを持ち、各フィールドが型を持っています。例えば:{ name :: String, greet :: String -> String }はJavaScriptオブジェクトの、String型のnameと、String型を取りString型を返す関数greetの2フィールドに正確に対応します。

代数的データ型(タグ付き共用体)

代数的データ型は1つ以上のコンストラクタから構成されます。各コンストラクタは0個以上の引数を取ります。

代数的データ型はコンストラクタからのみ作成され、パターンマッチによって解体されます(さらに徹底的なパターンマッチの方法については後に提示します)。

例をお見せします:

data Foo = Foo | Bar String

runFoo :: Foo -> String
runFoo Foo = "It's a Foo"
runFoo (Bar s) = "It's a Bar. The string is " <> s

main = do
  log (runFoo Foo)
  log (runFoo (Bar "Test"))

上記の例で、Fooは2つのコンストラクタを持つ代数的データ型です。1つ目のコンストラクタFooは引数を取らず、2つ目のコンストラクタBarはString型の引数を1つ取ります。

runFooは代数的データ型のコンストラクタを発見する例になっています。また、最後の2行でFoo型の値を作成しています。

ユーザ定義型

ユーザ定義型はdataキーワードによって表現されるデータ型と似ていますが、1つの引数を取るコンストラクタを1つ持つように制限されています。ユーザ定義型はnewtypeキーワードによって表現されます。

newtype Percentage = Percentage Number

ユーザ定義型は、ランタイムではその基となったデータ型と同じです。例えば、Percentage型の値は、ランタイムではただのJavaScriptの数値です。

ユーザ定義型は、型検査によって、その基となった型とは異なるものとして扱われます。例えば、Number型を受け取る関数にPercentage型を渡すことは、型検査によって拒絶されます。

ユーザ定義型には、自身にクラスのインスタンスを割り当てることができます。例えば、PercentageShowインスタンスを持つことができます:

instance showPercentage :: Show Percentage where
  show (Percentage n) = show n <> "%"

関数

PureScriptの関数はJavaScriptのそれとほぼ対応しますが、確実に1つの引数を持つようになっています。

多相型

式は多相型を持つことができます:

identity x = x

identityは多相型のforall t0. t0 -> t0を持つ、と推論されます。これは、identityが何の型にでもなれるt0型の値を受け取り、同じ型の値を返すことを意味します。

以下のように型注釈をつけることもできます:

identity :: forall a. a -> a
identity x = x

列多相

多相性は型の抽象化に制限がなく、値は種に関して多相になるかもしれません。例えば、列やモナドなどです(種システムを参照してください。)。

例えば、以下の関数はレコードの2パラメータにアクセスします。

addProps o = o.foo + o.bar + 1

addPropsから推論された型は:

forall r. { foo :: Int, bar :: Int | r } -> Int

ここで、型変数rは種# Typeを持ちます。この種は型の列を表現しています。これはどんな型の指定でもインスタンス化することができます。

別の言い方をすると、addPropsfoobarという2つのプロパティに加え、どんなプロパティを持つレコードでもいいということです。

したがって、以下のアプリケーションがコンパイルできます:

addProps { foo: 1, bar: 2, baz: 3 }

addPropsはプロパティbazについて言及していないにも関わらず、です。しかし、以下のようにするとコンパイルできません:

addProps { foo: 1 }

プロパティbarが欠落しているからです。

高階多相型

forall量化子を関数アローの左側や、レコード型のフィールド、データコンストラクタ、型シノニムに書くことも可能です。

ほとんどの場合、この機能を使うときには型注釈が必要になります。

私達が多相関数を他の関数の引数として渡すことができる例をお見せします:

poly :: (forall a. a -> a) -> Boolean
poly f = (f 0 < 1) == f true

多相関数の型引数がNumberBooleanの両方にインスタンス化されることに注意してください。

polyの引数は多相でなければなりません。例えば、以下のようにすると失敗します:

test = poly (\n -> n + 1)

スコーレム化された変数aは必ずしもIntになるわけではないからです。

型の列は、名前付きの型から成る重複可能で順不同な集合です。重複するラベルは順番に型がまとめられ、NonEmptyListのようになります。これは概念的には、列は型レベルのMap Label (NonEmptyList Type)であると考えられるということです。

列はType種ではなく、k種に対しての# k種を持つので、値として存在することはできません。むしろ、列はレコード型やラベル付けされた別の型を定義するために型シグネチャとして使用されます。非順序型は便利です。

閉じた列を表現するためには、フィールドをカンマで区切ります。また、フィールドと型はコロン2つで区切ります:

( name :: String, age :: Number )

開いた列(新しいフィールドを追加するために他の行と統合する可能性がある)を表現するためには、特定の文字と行変数をパイプで区切ります:

( name :: String, age :: Number | r )

型シノニム(型同義語)

利便性のため、typeキーワードを用いて型の同義語を宣言することができます。型シノニムは型引数を取ることができますが、部分適用は許されていません。また、型シノニムは他の型の定義のためにも用いることができますが、再帰は許されていません

例えば:

-- 2フィールドを持つレコードの別名を作成
type Foo = { foo :: Number, bar :: Number }

-- 2フィールドは数値なので、加算する
addFoo :: Foo -> Number
addFoo o = o.foo + o.bar

-- 同じ形を持つ多相レコードの別名を作成
type Bar a = { foo :: a, bar :: a }
-- FooはBar Numberと等しい

-- 任意のBarのフィールドを関数に適用
combineBar :: forall a b. (a -> a -> b) -> Bar a -> b
combineBar f o = f o.foo o.bar

-- 複雑な関数型の別名を作成
type Baz = Number -> Number -> Bar Number

-- この関数は引数を2つ取り、2倍した値をレコードに入れて返す
mkDoubledFoo :: Baz
mkDoubledFoo foo bar = { foo: 2.0*foo, bar: 2.0*bar }

-- 任意のFoo内の値を2倍するように前の関数を実装する
-- (Bar NumberがFooと同じであることを思い出してください)
doubleFoo :: Foo -> Foo
doubleFoo = combineBar mkDoubledFoo

-- 複雑な副作用の列の記述を楽にする型シノニムを定義
-- これにより、副作用を列に追加することを受け入れます
type RandomConsoleEffects eff = ( random :: RANDOM, console :: CONSOLE | eff )
-- これは副作用をRANDOMとCONSOLEに制限します
type RandomConsoleEffect = RandomConsoleEffects ()

ユーザ定義型とは異なり、型シノニムは単に型の別名であり、基の型と区別されません。このため、型クラスのインスタンスを宣言するために使うことはできません。

制約のある型

多相型は1つ以上の制約の上に成り立っています。詳しくは型クラスの章を見てください。

型注釈

高階多相型と制約のある型を除いたほとんどの型は推論されますが、コロンを2つ用いた型注釈を任意で提供することができ、宣言として書くことも、式の後に書くこともできます。

-- Data.Semiringで定義する
one :: forall a. (Semiring a) => a

-- Intはone = 1であるSemiringのインスタンスなので、oneはIntになれます
int1 :: Int
int1 = one -- int1 = 1と同じ
-- もしくはNumberであっても、one = 1.0であるSemiringのインスタンスとして提供されます
number1 = one :: Number -- number1 = 1.0と同じ
-- また、この多相性を保つことができるので、任意のSemiringで動作します
-- (型注釈がなければ、これはデフォルトです)
semiring1 :: forall a. Semiring a => a
semiring1 = one
-- 他の型クラスの制約を設けることもできます
equal1 = one :: forall a. Semiring a => Eq a => a

種システム

種システムは以下の種を定義します:

  • 型の種 Type
  • 高階種 k1 -> k2
  • 列種 # k
  • ユーザ定義種 Control.Monad.Eff.Effectなど。この例は作用の種です。

列種

列の種# kはラベル付きの分類に使われ、種kの型の非順序集合です。

例えば、# Typeは型の列の種で、レコードの定義に使われます。そして、# Control.Monad.Eff.Effectは作用の列の種で、これは拡張可能の作用モナドControl.Monad.Eff.Effを定義するために使われます。

量化

型変数は型や列だけでなく型コンストラクタや行コンストラクタなどを表すこともでき、そのような種を持つ型変数はforall量化子によって束縛されます。