サーチ…
タイプの紹介
タイプはさまざまな種類のものを表すことができます。これは、単一のデータ、データのセット、または機能とすることができます。
F#では、型を2つのカテゴリにグループ化できます。
F#タイプ:
// Functions let a = fun c -> c // Tuples let b = (0, "Foo") // Unit type let c = ignore // Records type r = { Name : string; Age : int } let d = { Name = "Foo"; Age = 10 } // Discriminated Unions type du = | Foo | Bar let e = Bar // List and seq let f = [ 0..10 ] let g = seq { 0..10 } // Aliases type MyAlias = string
.NETの種類
- 組み込み型(int、bool、string、...)
- クラス、構造体およびインタフェース
- 代表者
- 配列
タイプ略語
タイプの省略形を使用すると、既存のタイプにエイリアスを作成して、より意味のある感覚を与えることができます。
// Name is an alias for a string
type Name = string
// PhoneNumber is an alias for a string
type PhoneNumber = string
次に、他のタイプと同様にエイリアスを使用できます。
// Create a record type with the alias
type Contact = {
Name : Name
Phone : PhoneNumber }
// Create a record instance
// We can assign a string since Name and PhoneNumber are just aliases on string type
let c = {
Name = "Foo"
Phone = "00 000 000" }
printfn "%A" c
// Output
// {Name = "Foo";
// Phone = "00 000 000";}
注意してください。エイリアスは型の一貫性をチェックしません。つまり、同じタイプをターゲットとする2つのエイリアスを互いに割り当てることができます。
let c = {
Name = "Foo"
Phone = "00 000 000" }
let d = {
Name = c.Phone
Phone = c.Name }
printfn "%A" d
// Output
// {Name = "00 000 000";
// Phone = "Foo";}
タイプはF#で作成され、typeキーワードを使用します
F#
では、 type
キーワードを使用して異なる種類の型を作成します。
- タイプエイリアス
- 差別化されたユニオンタイプ
- レコードタイプ
- インタフェースタイプ
- クラスの種類
- 構造体の型
可能であれば、同等のC#
コードを使用した例:
// Equivalent C#:
// using IntAliasType = System.Int32;
type IntAliasType = int // As in C# this doesn't create a new type, merely an alias
type DiscriminatedUnionType =
| FirstCase
| SecondCase of int*string
member x.SomeProperty = // We can add members to DU:s
match x with
| FirstCase -> 0
| SecondCase (i, _) -> i
type RecordType =
{
Id : int
Name : string
}
static member New id name : RecordType = // We can add members to records
{ Id = id; Name = name } // { ... } syntax used to create records
// Equivalent C#:
// interface InterfaceType
// {
// int Id { get; }
// string Name { get; }
// int Increment (int i);
// }
type InterfaceType =
interface // In order to create an interface type, can also use [<Interface>] attribute
abstract member Id : int
abstract member Name : string
abstract member Increment : int -> int
end
// Equivalent C#:
// class ClassType : InterfaceType
// {
// static int increment (int i)
// {
// return i + 1;
// }
//
// public ClassType (int id, string name)
// {
// Id = id ;
// Name = name ;
// }
//
// public int Id { get; private set; }
// public string Name { get; private set; }
// public int Increment (int i)
// {
// return increment (i);
// }
// }
type ClassType (id : int, name : string) = // a class type requires a primary constructor
let increment i = i + 1 // Private helper functions
interface InterfaceType with // Implements InterfaceType
member x.Id = id
member x.Name = name
member x.Increment i = increment i
// Equivalent C#:
// class SubClassType : ClassType
// {
// public SubClassType (int id, string name) : base(id, name)
// {
// }
// }
type SubClassType (id : int, name : string) =
inherit ClassType (id, name) // Inherits ClassType
// Equivalent C#:
// struct StructType
// {
// public StructType (int id)
// {
// Id = id;
// }
//
// public int Id { get; private set; }
// }
type StructType (id : int) =
struct // In order create a struct type, can also use [<Struct>] attribute
member x.Id = id
end
型推論
了承
この例は、この記事の型推論
型推論とは何ですか?
型推論は、コンパイラがどの型が使用されているのか、どこでどこを推測することができるメカニズムです。このメカニズムは、「Hindley-Milner」または「HM」と呼ばれるアルゴリズムに基づいています。単純値と関数値の型を決定するための規則のいくつかを以下に示します。
- リテラルを見てください
- 関数や他の値と相互作用するものを見てください
- 任意の明示的な型制約を見てください
- どこにも制約がない場合、ジェネリック型に自動的に一般化されます
リテラルを見てください
コンパイラは、リテラルを調べて型を推論することができます。リテラルがintで、 "x"を追加する場合は、 "x"もintでなければなりません。しかし、リテラルが浮動小数点であり、 "x"を浮動小数点に追加する場合、 "x"も浮動小数点でなければなりません。
ここではいくつかの例を示します。
let inferInt x = x + 1
let inferFloat x = x + 1.0
let inferDecimal x = x + 1m // m suffix means decimal
let inferSByte x = x + 1y // y suffix means signed byte
let inferChar x = x + 'a' // a char
let inferString x = x + "my string"
関数とそれが相互作用する他の値を見てください
リテラルがどこにもない場合、コンパイラはそれらが相互作用する関数や他の値を分析することによって型を解こうとします。
let inferInt x = x + 1
let inferIndirectInt x = inferInt x //deduce that x is an int
let inferFloat x = x + 1.0
let inferIndirectFloat x = inferFloat x //deduce that x is a float
let x = 1
let y = x //deduce that y is also an int
任意の明示的な型制約または注釈を見てください
明示的な型制約または注釈が指定されている場合、コンパイラはそれらを使用します。
let inferInt2 (x:int) = x // Take int as parameter
let inferIndirectInt2 x = inferInt2 x // Deduce from previous that x is int
let inferFloat2 (x:float) = x // Take float as parameter
let inferIndirectFloat2 x = inferFloat2 x // Deduce from previous that x is float
自動一般化
結局のところ、制約が見つからなければ、コンパイラはタイプを汎用にするだけです。
let inferGeneric x = x
let inferIndirectGeneric x = inferGeneric x
let inferIndirectGenericAgain x = (inferIndirectGeneric x).ToString()
型推論で間違っていることがあるもの
型推論は完璧ではありません。時々、コンパイラは何をすべきかという手がかりを持っていません。繰り返しますが、何が起きているのかを理解することは、コンパイラを殺すのではなく、静かにするのに役立ちます。タイプエラーの主な理由のいくつかを次に示します。
- 順不同の宣言
- 情報が不十分
- オーバーロードされたメソッド
順不同の宣言
基本的な規則は、関数を使用する前に関数を宣言する必要があることです。
このコードは失敗します:
let square2 x = square x // fails: square not defined
let square x = x * x
しかしこれは大丈夫です:
let square x = x * x
let square2 x = square x // square already defined earlier
再帰的または同時の宣言
「順不同」問題の変形は、互いに参照しなければならない再帰関数または定義で発生します。この場合、並べ替えの手間はかかりません。コンパイラを助けるために追加のキーワードを使用する必要があります。
関数がコンパイルされているときは、関数識別子は本体で使用できません。したがって、単純な再帰関数を定義すると、コンパイラエラーが発生します。修正は、関数定義の一部として "rec"キーワードを追加することです。例えば:
// the compiler does not know what "fib" means
let fib n =
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
// error FS0039: The value or constructor 'fib' is not defined
これは再帰的であることを示すために "rec fib"が追加された固定バージョンです:
let rec fib n = // LET REC rather than LET
if n <= 2 then 1
else fib (n - 1) + fib (n - 2)
情報が不十分
時々、コンパイラは型を決定するのに十分な情報を持っていません。次の例では、コンパイラーはLengthメソッドがどのタイプの作業を行うかを認識していません。しかしそれはそれを一般的にすることもできないので、それは不平を言う。
let stringLength s = s.Length
// error FS0072: Lookup on object of indeterminate type
// based on information prior to this program point.
// A type annotation may be needed ...
これらの種類のエラーは、明示的な注釈で修正できます。
let stringLength (s:string) = s.Length
オーバーロードされたメソッド
.NETで外部クラスやメソッドを呼び出すと、オーバーロードのためにエラーが発生することがよくあります。
以下のようなconcatの例のような多くの場合、外部関数のパラメータに明示的に注釈を付けて、コンパイラがどのオーバーロードされたメソッドを呼び出すかを知る必要があります。
let concat x = System.String.Concat(x) //fails
let concat (x:string) = System.String.Concat(x) //works
let concat x = System.String.Concat(x:string) //works
オーバーロードされたメソッドの引数名が異なる場合があります。この場合、引数に名前を付けることによってコンパイラに手がかりを与えることもできます。次に、StreamReaderコンストラクタの例を示します。
let makeStreamReader x = new System.IO.StreamReader(x) //fails
let makeStreamReader x = new System.IO.StreamReader(path=x) //works