「コンパイルできれば正しい」という言葉がありますが、コードをコンパイルすること自体が非常に困難な場合があります!そこで、このページではF#コードのトラブルシューティングを支援することに焦点を当てています。
まず、トラブルシューティングに関する一般的なアドバイスと、初心者がよく犯す最も一般的なエラーについて説明します。その後、一般的なエラーメッセージそれぞれについて詳しく説明し、それらがどのように発生するか、そしてどのように修正するかの例を示します。
トラブルシューティングの一般的なガイドライン
最も重要なことは、F#がどのように機能するかを正確に理解するために時間と労力を費やすことです。特に、関数と型システムに関わる中核的な概念について理解することが大切です。ですので、「関数型思考」と「F#の型を理解する」のシリーズを何度も読み返し、例を試し、本格的なコーディングを始める前にこれらのアイデアに慣れてください。関数と型の仕組みを理解していないと、コンパイラエラーが全く意味をなさないものになってしまいます。
C#のような命令型言語から来た場合、デバッガーに頼って不正確なコードを見つけて修正するという悪習慣を身につけている可能性があります。F#では、コンパイラがより厳格であるため、そこまで到達しないかもしれません。もちろん、コンパイラを「デバッグ」してその処理をステップ実行するツールはありません。コンパイラエラーをデバッグするための最良のツールは頭脳であり、F#はそれを使うことを強制します!
それでも、初心者がよく犯す一連の非常に一般的なエラーがあります。ここでそれらを簡単に説明します。
関数を呼び出す際にかっこを使用しない
F#では、空白文字が関数パラメータの標準的な区切り文字です。かっこを使用する必要はほとんどなく、特に関数を呼び出す際にはかっこを使用しないでください。
let add x y = x + y
let result = add (1 2) //間違い
// error FS0003: この値は関数ではないため、適用できません
let result = add 1 2 //正しい
タプルと複数のパラメータを混同しない
カンマがある場合、それはタプルです。そして、タプルは2つのオブジェクトではなく1つのオブジェクトです。そのため、間違った型のパラメータを渡している、あるいはパラメータが少なすぎるというエラーが発生します。
addTwoParams (1,2) // 2つの引数の代わりに1つのタプルを渡そうとしている
// error FS0001: この式に必要な型は 'int' ですが、
// ここでは次の型が指定されています 'int * int'
コンパイラは(1,2)
をタプルとして扱い、それを addTwoParams
に渡そうとします。そして、addTwoParams
の最初のパラメータがint型であるのに対し、タプルを渡そうとしているとクレームをつけます。
1つのタプルを期待する関数に2つの引数を渡そうとすると、別の分かりにくいエラーが発生します。
addTuple 1 2 // 1つのタプルの代わりに2つの引数を渡そうとしている
// error FS0003: この値は関数ではないため、適用できません。
引数が少なすぎたり多すぎたりしないよう注意する
F#コンパイラは、関数に渡す引数が少なすぎても文句を言いません(実際、「部分適用」は重要な機能です)。しかし、何が起こっているのか理解していないと、後で奇妙な「型の不一致」エラーがよく発生します。
同様に、引数が多すぎる場合のエラーは、通常、より直接的なエラーではなく「この値は関数ではありません」となります。
printf
ファミリーの関数は、この点で非常に厳格です。引数の数は正確でなければなりません。
これは非常に重要なトピックです。部分適用がどのように機能するかを理解することが重要です。詳細な議論については、「関数型思考」シリーズを参照してください。
リストの区切り文字にはセミコロンを使用する
F#が明示的な区切り文字を必要とする数少ない場所(リストやレコードなど)では、セミコロンが使用されます。カンマは決して使用されません。(繰り返しになりますが、カンマはタプル用であることを思い出してください)。
let list1 = [1,2,3] // 間違い! これは3要素のタプルを含む
// 1要素のリストです
let list1 = [1;2;3] // 正しい
type Customer = {Name:string, Address: string} // 間違い
type Customer = {Name:string; Address: string} // 正しい
!をNOTとして、!=を不等号として使用しない
感嘆符記号は "NOT" 演算子ではありません。可変参照の参照解除演算子です。誤って使用すると、次のエラーが発生します:
let y = true
let z = !y
// error FS0001: この式に必要な型は ''a ref' ですが、
// ここでは次の型が指定されています 'bool'
// '!' 演算子は ref セルの逆参照に使用されます。
// ここに 'not expr' を使用することをご検討ください。
正しい構文は、 not
キーワードを使用することです。C構文ではなく、SQLやVB構文を思い浮かべてください。
let y = true
let z = not y //正しい
そして、「等しくない」には、SQLやVBと同様に <>
を使用します。
let z = 1 <> 2 //正しい
代入に = を使用しない
可変値を使用する場合、代入操作は <-
と記述します。等号記号を使用すると、エラーが発生しないかもしれませんが、予期しない結果になる可能性があります。
let mutable x = 1
x = x + 1 // falseを返します。xはx+1と等しくありません
x <- x + 1 // x+1をxに代入します
隠れたタブ文字に注意する
インデントのルールは非常に簡単で、すぐに慣れることができます。ただし、タブは使用できず、スペースのみを使用する必要があります。
let add x y =
{tab} x + y
// => error FS1161: F# コードにタブを使用するには、#indent "off" オプションを使用する必要があります
エディタでタブをスペースに変換するように設定してください。また、他の場所からコードを貼り付ける場合は注意が必要です。コードの一部で執拗に問題が発生し続ける場合は、空白を削除して再度追加してみてください。
単純な値を関数値と間違えない
関数ポインタやデリゲートを作成しようとしている場合、既に評価済みの単純な値を誤って作成しないように注意してください。
再利用可能なパラメータなしの関数が必要な場合は、明示的にunitパラメータを渡すか、ラムダとして定義する必要があります。
let reader = new System.IO.StringReader("hello")
let nextLineFn = reader.ReadLine() //間違い
let nextLineFn() = reader.ReadLine() //正しい
let nextLineFn = fun() -> reader.ReadLine() //正しい
let r = new System.Random()
let randomFn = r.Next() //間違い
let randomFn() = r.Next() //正しい
let randomFn = fun () -> r.Next() //正しい
パラメータなしの関数についての詳細な議論は、「関数型思考」シリーズを参照してください。
「情報が不足している」エラーのトラブルシューティングのヒント
F#コンパイラは現在、左から右への1パスコンパイラであるため、まだ解析されていないプログラムの後半にある型情報はコンパイラにとって利用できません。
これにより、「FS0072: 不確定の型のオブジェクトに対する参照です」や「FS0041: 固有のオーバーロードを決定することができませんでした」などの多くのエラーが発生する可能性があります。これらの特定のケースに対する推奨される修正方法は以下で説明されていますが、コンパイラが型の不足や情報不足を訴えている場合に役立つ一般的な原則がいくつかあります。これらのガイドラインは以下の通りです:
- 使用する前に定義する(これには、ファイルが正しい順序でコンパイルされていることを確認することも含まれます)
- 「既知の型」を持つものを「未知の型」を持つものより先に配置する。特に、パイプや類似の連鎖関数を、型付けされたオブジェクトが先に来るように並べ替えることができるかもしれません。
- 必要に応じて型注釈を行う。一般的なテクニックとして、すべてが機能するまで型注釈を追加し、その後、必要最小限になるまで1つずつ取り除いていくというものがあります。
可能であれば、型注釈を避けるようにしてください。見た目が良くないだけでなく、コードをより脆弱にします。明示的な依存関係がない方が、型の変更がはるかに容易になります。
F# コンパイラエラー
よくあるエラーの一覧 (エラー番号順)
これは、文書化に値すると思われる主要なエラーの一覧です。明らかなエラーは文書化せず、初心者にとってはわかりにくいと思われるものだけを扱います。
今後もリストに追加していく予定であり、追加すべき項目についての提案を歓迎します。
- FS0001: この式に必要な型は 'X' ですが、ここでは次の型が指定されています 'Y'
- FS0003: この値は関数ではないため、適用できません。
- FS0008: このランタイム型変換またはランタイム型テストには、不確定の型が使用されています
- FS0010: 予期しない 識別子 です 束縛内
- FS0010: 構造化コンストラクトが不完全です
- FS0013: 型 X から型 Y の静的型変換には、不確定の型が使用されています
- FS0020: この式の結果の型は 'X' で、暗黙的に無視されます
- FS0030: 値の制限
- FS0035: このコンストラクトは使用されなくなりました
- FS0039: フィールド、コンストラクター、またはメンバー 'X' を定義していません
- FS0041: 固有のオーバーロードを決定することができませんでした
- FS0049: 通常、大文字の変数識別子はパターンに使用できません
- FS0072: 不確定の型のオブジェクトに対する参照です
- FS0588: この 'let' に続くブロックが完了していません
FS0001: この式に必要な型は 'X' ですが、ここでは次の型が指定されています 'Y'
これはおそらく最も遭遇するエラーでしょう。さまざまな状況で発生するため、最も一般的な問題を例と解決方法と共にまとめています。エラーメッセージには通常、問題が何であるかが明示的に記載されているため、注意してください。
エラーメッセージ | 考えられる原因 |
---|---|
この式に必要な型は 'float' ですが、ここでは次の型が指定されています 'int' | A. int と float は混用できない |
型 'int' には必要な (実数または組み込み) メンバー 'DivideByInt' がないため、'X' ではサポートされません | A. int と float は混用できない |
型 'X' は、型 byte,int16,int32, ... のいずれとも互換性がありません | B. 間違った数値型を使用している |
型 (関数型) は型 (単純な型) と一致しません 注意: 関数型には矢印が含まれており、たとえば 'a -> 'b のような形式になります。 |
C. 関数に引数を渡しすぎている |
この式に必要な型は (関数型) ですが、ここでは次の型が指定されています (単純な型) | C. 関数に引数を渡しすぎている |
型が一致しません。 (N項の関数) という指定が必要ですが、(N-1項の関数) が指定されました。 | C. 関数に引数を渡しすぎている |
この式に必要な型は (単純な型) ですが、ここでは次の型が指定されています (関数型) | D. 関数への引数が足りない |
この式に必要な型は (型) ですが、ここでは次の型が指定されています (別の型) | E. 単純な型の不一致 F. 分岐やマッチでの返り値の型の不一致 G. 関数内で起こる型推論の影響に注意 |
型が一致しません。 (単純な型) という指定が必要ですが、(タプル型) が指定されました。 注意: タプル型には星が含まれており、たとえば 'a * 'b のような形式になります。 |
H. スペースやセミコロンではなくカンマを使ってしまっていませんか? |
型が一致しません。 (タプル型) という指定が必要ですが、(別のタプル型) が指定されました。 | I. タプルの比較やパターンマッチングには同じ型が必要 |
この式に必要な型は ''a ref' ですが、ここでは次の型が指定されています 'X' | J. "not" 演算子として ! を使用しない |
型 (型) は型 (別の型) と一致しません | K. 演算子の優先順位(特に関数とパイプ) |
この式に必要な型は (モナド型) ですが、ここでは次の型が指定されています ''b * 'c'' | L. コンピュテーション式における let! エラー |
A. int と float は混用できない
C# やほとんどの命令型言語とは異なり、F# では int と float を式の中で混用できません。次のようにしようとすると、型エラーが発生します。
1 + 2.0 //間違い
// => error FS0001: この式に必要な型は 'float' ですが、ここでは次の型が指定されています 'int'
解決方法は、まず int を float
にキャストすることです。
float 1 + 2.0 //正しい
この問題は、ライブラリ関数や他の場所でも発生する可能性があります。たとえば、int のリストに対して average
を適用することはできません。
[1..10] |> List.average // 間違い
// => error FS0001: 型 'int' には必要な (実数または組み込み) メンバー 'DivideByInt' がないため、
// 'List.average' ではサポートされません
以下のように、最初に各 int を float にキャストする必要があります。
[1..10] |> List.map float |> List.average //正しい
[1..10] |> List.averageBy float //正しい (averageByを使う)
B. 間違った数値型を使用している
数値キャストが失敗すると、「互換性がありません」というエラーが発生します。
printfn "hello %i" 1.0 // float ではなく int でなければならない
// error FS0001: 型 'float' は、printf 形式の書式指定文字列の使用によって生じる型
// byte,int16,int32,... のいずれとも互換性がありません
もし問題なければ、キャストするという方法が考えられます。
printfn "hello %i" (int 1.0)
C. 関数に引数を渡しすぎている
let add x y = x + y
let result = add 1 2 3
// ==> error FS0001: 型 ''a -> 'b' は型 'int' と一致しません
エラーメッセージにヒントが隠れています。
解決方法は、引数を 1 つ削除することです!
printf
に引数を渡しすぎることでも同様のエラーが発生します。
printfn "hello" 42
// ==> error FS0001: この式に必要な型は ''a -> 'b'
// ですが、ここでは次の型が指定されています 'unit'
printfn "hello %i" 42 43
// ==> Error FS0001: 型が一致しません。 ''a -> 'b -> 'c' という指定が必要ですが、
// ''a -> unit' が指定されました。型 ''a -> 'b' は型 'unit' と一致しません
printfn "hello %i %i" 42 43 44
// ==> Error FS0001: 型が一致しません。 ''a -> 'b -> 'c -> 'd' という指定が必要ですが、
// ''a -> 'b -> unit' が指定されました。型 ''a -> 'b' は型 'unit' と一致しません
D. 関数への引数が足りない
関数を呼び出すときに、必要な引数が足りないと、部分適用と呼ばれる状態になります。 この部分適用を後で使うと、単純な型ではないためエラーが発生します。
let reader = new System.IO.StringReader("hello");
let line = reader.ReadLine //間違いだが、コンパイラーは文句を言わない
printfn "The line is %s" line //ここでコンパイラー・エラー!
// ==> error FS0001: この式に必要な型は 'string'
// ですが、ここでは次の型が指定されています 'unit -> string'
これは、上で見た ReadLine
のような、unit
パラメータを必要とする一部の .NET ライブラリ関数でよく発生します。
解決方法は、正しい数の引数を渡すことです。結果の値の型が実際に単純な型であることを確認するために、型を確認してください。
ReadLine
の場合は、()
という引数を渡すことで解決します。
let line = reader.ReadLine() //正しい
printfn "The line is %s" line //コンパイラー・エラーなし
E. 単純な型の不一致
最も単純なケースは、型が間違っているか、print のフォーマット文字列で間違った型を使っていることです。
printfn "hello %s" 1.0
// => error FS0001: この式に必要な型は 'string'
// ですが、ここでは次の型が指定されています 'float'
F. 分岐やマッチでの返り値の型の不一致
よくある間違いとして、分岐やマッチ式がある場合、各分岐は必ず同じ型を返さなければなりません。そうでないと、型エラーが発生します。
let f x =
if x > 1 then "hello"
else 42
// => error FS0001: if' 式のすべてのブランチは、最初のブランチの型 (ここでは 'string')
// に暗黙的に変換可能な値を返す必要があります。このブランチの返す値の型は 'int' です。
let g x =
match x with
| 1 -> "hello"
| _ -> 42
// error FS0001: パターン マッチ式のすべてのブランチは、最初のブランチの型 (ここでは 'string')
// に暗黙的に変換可能な値を返す必要があります。このブランチが返す値の型は 'int' です。
当然、最も簡単な解決方法は、各分岐が同じ型を返すようにすることです。
let f x =
if x > 1 then "hello"
else "42"
let g x =
match x with
| 1 -> "hello"
| _ -> "42"
"else" ブランチがない場合、unit
を返すものとみなされるので、"true" ブランチも unit
を返すようにする必要があります。
let f x =
if x > 1 then "hello"
// error FS0001: 'if' 式に 'else' ブランチがありません。'then' ブランチは型 'string' です。
// 'if' はステートメ ントではなく式であるため、
// 同じ型の値を返す 'else' ブランチを追加してください。
両方の分岐が同じ型を返せない場合は、両方の型を保持できる新しい共用体型を作成する必要があるかもしれません。
type StringOrInt = | S of string | I of int // 新しい共用体型
let f x =
if x > 1 then S "hello"
else I 42
G. 関数内で起こる型推論の影響に注意
ある関数が、コード全体に波及する予期しない型推論を引き起こすことがあります。
たとえば、以下のコードでは、一見無害な print のフォーマット文字列によって、 doSomething
関数が文字列を受け取ると型推論させてしまっています。
let doSomething x =
// 何らかの処理を行う
printfn "x is %s" x
// さらに何らかの処理を行う
doSomething 1
// => error FS0001: この式に必要な型は 'string'
// ですが、ここでは次の型が指定されています 'int'
修正方法は、関数シグネチャを確認して、問題の根源を見つけるまで掘り下げることです。また、可能な限り汎用的な型を使い、型注釈は必要なければ避けるようにしましょう。
H. スペースやセミコロンではなくカンマを使ってしまっていませんか?
F# 初心者によくあるミスとして、関数引数を区切る際に、スペースやセミコロンの代わりにカンマを誤って使ってしまうことが挙げられます。
// 2つの引数を取る関数定義
let add x y = x + 1
add(x,y) // FS0001: この式に必要な型は 'int'
// ですが、ここでは次の型が指定されています ''a * 'b'
修正方法: カンマを使わないようにしましょう!
add x y // OK
ただし、カンマが使用されるケースが 1 つあります。それは .NET ライブラリ関数を呼ぶときです。 これらの関数はすべてタプルを引数として取るため、カンマを使う形式が正しいのです。 実際、C# から呼び出す場合と同じ見た目になります。
// 正しい
System.String.Compare("a","b")
// 正しくない
System.String.Compare "a" "b"
I. タプルの比較やパターンマッチングには同じ型が必要
異なる型のタプルは比較できません。 int * int
型のタプルと int * string
型のタプルを比較しようとすると、エラーが発生します。
let t1 = (0, 1)
let t2 = (0, "hello")
t1 = t2
// => error FS0001: 型が一致しません。 'int * int'
// という指定が必要ですが、 'int * string'
// が指定されました。型 'int' は型 'string' と一致しません
また、長さも同じである必要があります。
let t1 = (0, 1)
let t2 = (0, 1, "hello")
t1 = t2
// => error FS0001: 型が一致しません。型の長さ 2 のタプルが必要です int * int
// ただし、型の長さ 3 のタプルが指定された場合 int * int * string
束縛におけるタプルのパターンマッチングでも同様の問題が発生する可能性があります。
let x,y = 1,2,3
// => error FS0001: 型が一致しません。型の長さ 2 のタプルが必要です 'a * 'b
// ただし、型の長さ 3 のタプルが指定された場合 int * int * int
let f (x,y) = x + y
let z = (1,"hello")
let result = f z
// => error FS0001: 型が一致しません。 'int * int'
// という指定が必要ですが、'int * string'
// が指定されました。型 'int' は型 'string' と一致しません
J. "not" 演算子として ! を使用しない
!
を "not" 演算子として使用すると、 "ref" という単語を含む型エラーが発生します。
let y = true
let z = !y //間違い
// => error FS0001: この式に必要な型は ''a ref' ですが、ここでは次の型が指定されています 'bool'
// '!' 演算子は ref セルの逆参照に使用されます。ここに 'not expr' を使用することをご検討ください。
解決策は、代わりに "not" キーワードを使用することです。
let y = true
let z = not y //正しい
K. 演算子の優先順位(特に関数とパイプ)
演算子の優先順位を間違えると、型エラーが発生する可能性があります。一般に、関数適用は他の演算子と比較して最も優先順位が高いため、以下のようなケースでエラーが発生します。
String.length "hello" + "world"
// => error FS0001: 型 'string' は型 'int' と一致しません
// 実際に起こっていること
(String.length "hello") + "world"
解決策はかっこを使用することです。
String.length ("hello" + "world") // 訂正された
逆に、パイプ演算子は他の演算子と比較して優先順位が低くなります。
let result = 42 + [1..10] |> List.sum
// => => error FS0001: 型 ''a list' は型 'int' と一致しません
// 実際に起こっていること
let result = (42 + [1..10]) |> List.sum
ここでも、解決策はかっこを使用することです。
let result = 42 + ([1..10] |> List.sum)
L. コンピュテーション式(モナド)における let! エラー
以下は簡単なコンピュテーション式です。
type Wrapper<'a> = Wrapped of 'a
type wrapBuilder() =
member this.Bind (wrapper:Wrapper<'a>) (func:'a->Wrapper<'b>) =
match wrapper with
| Wrapped(innerThing) -> func innerThing
member this.Return innerThing =
Wrapped(innerThing)
let wrap = new wrapBuilder()
しかし、これを使用しようとするとエラーが発生します。
wrap {
let! x1 = Wrapped(1) // <== ここでエラー
let! y1 = Wrapped(2)
let z1 = x + y
return z
}
// error FS0001: この式に必要な型は 'Wrapper<'a>' ですが、ここでは次の型が指定されています
// 'Wrapper<int> * ('b -> ('c -> Wrapper<'d>) -> Wrapper<'d>)'
理由は、 Bind
が2つのパラメータではなく、タプル (wrapper,func)
を期待しているためです。(F#のドキュメントでbindのシグネチャを確認してください)。
解決策は、 bind 関数を変更して、(単一の)パラメータとしてタプルを受け取るようにすることです。
type wrapBuilder() =
member this.Bind (wrapper:Wrapper<'a>, func:'a->Wrapper<'b>) =
match wrapper with
| Wrapped(innerThing) -> func innerThing
FS0003: この値は関数ではないため、適用できません。
このエラーは通常、関数に多すぎる引数を渡した時に発生します。
let add1 x = x + 1
let x = add1 2 3
// ==> error FS0003: この値は関数ではないため、適用できません。
また、演算子のオーバーロードを行う際に、その演算子を前置または中置演算子として使用できない場合にも発生することがあります。
let (!!) x y = x + y
(!!) 1 2 // ok
1 !! 2 // 失敗 !! 中置演算子として使用できない
// error FS0003: この値は関数ではないため、適用できません。
FS0008: このランタイム型変換またはランタイム型テストには、不確定の型が使用されています
これは多くの場合、 :?
演算子を使用して型のマッチングを試みる際に見られます。
let detectType v =
match v with
| :? int -> printfn "this is an int"
| _ -> printfn "something else"
// error FS0008: この型 'a から型 int へのランタイム型変換またはランタイム型テストには、
// このプログラムの場所の前方にある情報に基づく不確定の型が使用されています。
// ランタイム型テストが許可されていない型もあります。型の注釈を増やしてください。
メッセージが問題を示しています。「ランタイム型テストが許可されていない型もあります。」
解決策は値を "box" 化することです。これにより参照型に強制され、その後型チェックを行うことができます。
let detectTypeBoxed v =
match box v with // "box v"を使用
| :? int -> printfn "this is an int"
| _ -> printfn "something else"
//テスト
detectTypeBoxed 1
detectTypeBoxed 3.14
FS0010: 予期しない 識別子 です 束縛内
通常、ブロック内の式の整列に関する「オフサイド」ルールを破ることによって引き起こされます。
//3456789
let f =
let x=1 // オフサイドラインは列3
x+1 // おっと! 列4から始めないでください
// error FS0010: 予期しない 識別子 です 束縛内。
// このポイントまたはその前にある構造化コンストラクトが不完全です
// または他のトークンを指定してください。
修正方法はコードを正しく整列させることです!
整列によって引き起こされる別の問題については、「FS0588: この 'let' に続くブロックが完了していません」も参照してください。
FS0010: 構造化コンストラクトが不完全です
クラスコンストラクタからかっこが抜けている場合によく発生します。
type Something() =
let field = ()
let x1 = new Something // error FS0010:
// この場所またはその前にある構造化コンストラクトが不完全です
let x2 = new Something() // OK!
演算子をかっこで囲むのを忘れた場合にも発生する可能性があります。
// 新しい演算子を定義
let (|+) a = -a
|+ 1 // error FS0010:
// 予期しない 挿入演算子 です
(|+) 1 // かっこ付き -- OK!
中置演算子の一方が欠けている場合にも発生する可能性があります。
|| true // error FS0010:
// 予期しない シンボル '||' です
false || true // OK
F#インタラクティブに名前空間定義を送ろうとした場合にも発生する可能性があります。インタラクティブコンソールは名前空間を許可しません。
namespace Customer // error FS0010:
// この場所またはその前にある構造化コンストラクトが不完全です
// 型を宣言
type Person= {First:string; Last:string}
FS0013: 型 X から型 Y の静的型変換には、不確定の型が使用されています
This is generally caused by implic
FS0020: この式の結果の型は 'X' で、暗黙的に無視されます
このエラーは一般的に2つの状況で見られます。
- ブロック内の最後の式ではない式
- 間違った代入演算子の使用
ブロック内の最後の式ではない式における FS0020
ブロック内で値を返すことができるのは最後の式のみです。それ以外はすべて unit を返す必要があります。そのため、これは通常、最後の関数ではない場所に関数がある場合に発生します。
let something =
2+2 // => FS0020: この式の結果の型は 'int' で、暗黙的に無視されます。
// 'ignore' を使用してこの値を明示的に破棄してください (例: 'expr |> ignore')。
// または 'let' を使用して結果を名前に束縛します (例: 'let result = expr')。
"hello"
簡単な修正方法は ignore
を使用することです。しかし、なぜ関数を使用してその結果を捨てているのか自問してみてください。バグかもしれません。
let something =
2+2 |> ignore // ok
"hello"
これはまた、C#を書いていると思い込んで、誤ってセミコロンを使って式を区切ろうとした場合にも発生します。
// 間違い
let result = 2+2; "hello";
// 修正
let result = 2+2 |> ignore; "hello";
代入演算子における FS0020
このエラーの別のバリエーションは、プロパティに代入する際に発生します。
この等式の結果の型は 'bool' で、暗黙的に破棄されます。
'let' を使用して結果を名前に束縛することを検討してください。
たとえば、'let result = expression' などとします。
意図的に値を変更する場合、'<-' 演算子を 'x <- expression' などと使用します。
このエラーが発生した場合、可変値に対する代入演算子 <-
と等価比較演算子 =
を混同している可能性が高いです。
// '=' と '<-'
let add() =
let mutable x = 1
x = x + 1 // warning FS0020
printfn "%d" x
修正方法は適切な代入演算子を使用することです。
// 修正
let add() =
let mutable x = 1
x <- x + 1
printfn "%d" x
FS0030: 値の制限
これは、F# が可能な限り自動でジェネリック型へ一般化しようとする機能に関連しています。
たとえば、次のようなコードがあった場合、
let id x = x
let compose f g x = g (f x)
let opt = None
F# の型推論は巧妙にジェネリック型を推測してくれます。
val id : 'a -> 'a
val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
val opt : 'a option
しかし、場合によっては F# コンパイラはコードが曖昧だと判断し、型を正しく推測できそうに見えても、より詳細な指定を要求することがあります。
let idMap = List.map id // error FS0030
let blankConcat = String.concat "" // error FS0030
ほとんどの場合、これは部分適用関数を定義しようとしたことが原因であり、最も簡単な修正方法は、欠けている引数を明示的に追加することです。
let idMap list = List.map id list // OK
let blankConcat list = String.concat "" list // OK
詳細は、「自動ジェネリック化」に関する Microsoft Learn 記事を参照してください。
FS0035: このコンストラクトは使用されなくなりました
F# の構文はここ数年で改善されており、古い F# の書籍や Webページからのサンプルを使用していると、このエラーが発生するかもしれません。正しい構文については、Microsoft Learn ドキュメントを参照してください。
let x = 10
let rnd1 = System.Random x // よい
let rnd2 = new System.Random(x) // よい
let rnd3 = new System.Random x // error FS0035
FS0039: フィールド、コンストラクター、またはメンバー 'X' を定義していません
このエラーは一般的に以下の4つの状況で見られます。
- 明らかに、何かが実際に定義されていないケース! また、タイプミスや大文字小文字の不一致がないことも確認してください。
- インターフェース
- 再帰
- 拡張メソッド
インターフェースにおける FS0039
F#ではすべてのインターフェースが「明示的」実装であり、「暗黙的」ではありません(「明示的インターフェース実装」の違いについての説明は、C#の「明示的なインターフェイスの実装」ドキュメントを参照してください)。
重要なポイントは、インターフェースメンバーが明示的に実装されている場合、通常のクラスインスタンスを通じてアクセスできず、インターフェースのインスタンスを通じてのみアクセスできるため、 :>
演算子を使用してインターフェース型にキャストする必要があるということです。
以下は、インターフェースを実装するクラスの例です。
type MyResource() =
interface System.IDisposable with
member this.Dispose() = printfn "disposed"
これは機能しません。
let x = new MyResource()
x.Dispose() // error FS0039: 型 'MyResource' は、フィールド、コンストラクター、
// またはメンバー 'Dispose' を定義していません。
修正方法は、以下のようにオブジェクトをインターフェースにキャストすることです。
// System.IDisposableにキャストすることで修正
(x :> System.IDisposable).Dispose() // OK
let y = new MyResource() :> System.IDisposable
y.Dispose() // OK
再帰における FS0039
以下は標準的なフィボナッチ実装です。
let fib i =
match i with
| 1 -> 1
| 2 -> 1
| n -> fib(n-1) + fib(n-2)
残念ながら、これはコンパイルされません。
error FS0039: 値またはコンストラクター 'fib' が定義されていません。
理由は、コンパイラが関数本体の中で「fib」という名前を見つけても、その時点ではまだ関数全体のコンパイルが終わっていないため、その関数のことを知らないからです!
修正方法は、 rec
キーワードを使用することです。
let rec fib i =
match i with
| 1 -> 1
| 2 -> 1
| n -> fib(n-1) + fib(n-2)
これは let
関数にのみ適用されることに注意してください。メンバー関数はこれを必要としません。なぜなら、スコープルールが少し異なるためです。
type FibHelper() =
member this.fib i =
match i with
| 1 -> 1
| 2 -> 1
| n -> fib(n-1) + fib(n-2)
拡張メソッドにおける FS0039
拡張メソッドを定義している場合、モジュールがスコープ内にないと使用できません。
これを示すための簡単な拡張メソッドを以下に示します。
module IntExtensions =
type System.Int32 with
member this.IsEven = this % 2 = 0
拡張メソッドを使用しようとすると、FS0039エラーが発生します。
let i = 2
let result = i.IsEven
// error FS0039: 型 'Int32' は、フィールド、コンストラクター、
// またはメンバー 'IsEven' を定義していません。
修正方法は、単に IntExtensions
モジュールを開くことです。
open IntExtensions // モジュールをスコープに入れる
let i = 2
let result = i.IsEven // 修正された!
FS0041: 固有のオーバーロードを決定することができませんでした
これは、複数のオーバーロードを持つ.NETライブラリ関数を呼び出す際に発生することがあります。
let streamReader filename = new System.IO.StreamReader(filename) // FS0041
この問題を解決するには複数の方法があります。一つの方法は、明示的な型注釈を使用することです。
let streamReader filename = new System.IO.StreamReader(filename:string) // OK
場合によっては、型注釈を避けるために名前付きパラメータを使用できます。
let streamReader filename = new System.IO.StreamReader(path=filename) // OK
または、型推論を助ける中間オブジェクトを作成することで、型注釈を必要とせずに解決できる場合もあります。
let streamReader filename =
let fileInfo = System.IO.FileInfo(filename)
new System.IO.StreamReader(fileInfo.FullName) // OK
FS0049: 通常、大文字の変数識別子はパターンに使用できません
パターンマッチングを行う際、タグのみで構成される純粋な F# の判別共用体と .NET の列挙型の間には微妙な違いがあることに注意してください。
純粋な F# の判別共用体:
type ColorUnion = Red | Yellow
let redUnion = Red
match redUnion with
| Red -> printfn "red" // 問題なし
| _ -> printfn "something else"
しかし、 .NET の列挙型では完全修飾名を使用する必要があります。
type ColorEnum = Green=0 | Blue=1 // enum
let blueEnum = ColorEnum.Blue
match blueEnum with
| Blue -> printfn "blue" // warning FS0049
| _ -> printfn "something else"
修正後のバージョン:
match blueEnum with
| ColorEnum.Blue -> printfn "blue"
| _ -> printfn "something else"
FS0072: 不確定の型のオブジェクトに対する参照です
これは、型が不明なオブジェクトに対して「ドット演算子を使用して」アクセスしようとした場合に発生します。
以下の例を考えてみましょう。
let stringLength x = x.Length // Error FS0072
コンパイラは "x" の型を知らないため、 Length
が有効なメソッドかどうかわかりません。
この問題を解決するには複数の方法があります。最も単純な方法は、明示的な型注釈を提供することです。
let stringLength (x:string) = x.Length // OK
しかし、場合によっては、コードの適切な再配置が役立つこともあります。たとえば、以下の例は動作するように見えます。人間から見れば、 List.map
関数が文字列のリストに適用されていることは明らかですが、なぜ x.Length
がエラーを引き起こすのでしょうか?
List.map (fun x -> x.Length) ["hello"; "world"] // Error FS0072
理由は、F#コンパイラが現在1パス・コンパイラであるため、プログラムの後半に存在する型情報は、まだ解析されていない場合は使用できないからです。
もちろん、常に明示的に注釈をつけることはできます。
List.map (fun x:string -> x.Length) ["hello"; "world"] // OK
しかし、より優雅な方法として、既知の型が先に来るように配置を変更し、コンパイラが次の句に移る前にそれらを処理できるようにする方法があります。多くの場合、この方法でも問題を解決できます。
["hello"; "world"] |> List.map (fun x -> x.Length) // OK
明示的な型注釈を避けるのが良い習慣なので、可能であればこのアプローチが最適です。
FS0588: この 'let' に続くブロックが完了していません
ブロック内の式をインデント解除することで「オフサイドルール」を破った場合に発生します。
//3456789
let f =
let x=1 // オフサイドラインは列3
x+1 // オフサイド! ボールより前に出た!
// error FS0588: この 'let' に続くブロックが完了していません。
// すべてのコード ブロックは式であり、結果を持つ必要があります。
// 'let' をブロック内の最後のコード要素にすることはできません。
// このブロックに明示的な結果を指定することを検討してください。
修正方法は、コードを正しく整列させることです。
整列によって引き起こされる別の問題については、「FS0010: 予期しない識別子です 束縛内」も参照してください。