@@ -21,6 +21,14 @@ F#の評価機能を追加できます。
2121 理由の1つとしては `StackOverflowException` を処理する方法がないため、
2222 出来の悪いスクリプトによってはホストプロセスが停止させられてしまう
2323 場合があるからです。
24+ **.NET APIを通じてF# Interactiveを呼び出すとしても、 `--shadowcopyreferences`
25+ オプションは無視されることを覚えておきましょう。**
26+ 詳細な議論については、[このスレッド](https://github.com/fsharp/FSharp.Compiler.Service/issues/292)
27+ に目を通してみてください。
28+ **注意:** もし`FSharp.Core.dll` が見つからないというエラーが出て `FsiEvaluationSession.Create`
29+ に失敗した場合、 `FSharp.Core.sigdata` と `FSharp.Core.optdata` というファイルを追加してください。
30+ 詳しい内容は[こちら](https://fsharp.github.io/FSharp.Compiler.Service/ja/corelib.html)
31+ にあります。
2432
2533しかしそれでもF# InteractiveサービスにはF# Interactiveを実行ファイルに埋め込んで
2634実行出来る(そしてアプリケーションの各機能とやりとり出来る)、あるいは
@@ -34,6 +42,7 @@ F# Interactiveの開始
3442*)
3543
3644#r " FSharp.Compiler.Service.dll"
45+ open Microsoft.FSharp .Compiler .SourceCodeServices
3746open Microsoft.FSharp .Compiler .Interactive .Shell
3847
3948(**
@@ -43,10 +52,11 @@ F# Interactiveとやりとりするには、入出力を表すストリームを
4352*)
4453open System
4554open System.IO
55+ open System.Text
4656
4757// 入出力のストリームを初期化
48- let sbOut = new Text. StringBuilder()
49- let sbErr = new Text. StringBuilder()
58+ let sbOut = new StringBuilder()
59+ let sbErr = new StringBuilder()
5060let inStream = new StringReader( " " )
5161let outStream = new StringWriter( sbOut)
5262let errStream = new StringWriter( sbErr)
@@ -62,43 +72,143 @@ let fsiSession = FsiEvaluationSession.Create(fsiConfig, allArgs, inStream, outSt
6272コードの評価および実行
6373----------------------
6474
65- F# Interactiveサービスにはサービスとやりとりするためのメソッドが2つ用意されています 。
66- 1つは `EvalExpression` で、式を評価してその結果を返します。
67- 結果には戻り値が( `obj` として)含まれる他、値に対して静的に推測された型も含まれます :
75+ F# Interactiveサービスにはコードを評価するためのメソッドがいくつか用意されています 。
76+ 最初の1つは `EvalExpression` で、式を評価してその結果を返します。
77+ 結果には戻り値が( `obj` として)含まれる他、値に対して静的に推論された型も含まれます :
6878*)
6979/// 式を評価して結果を返す
7080let evalExpression text =
7181 match fsiSession.EvalExpression( text) with
7282 | Some value -> printfn " %A " value.ReflectionValue
7383 | None -> printfn " 結果が得られませんでした!"
84+
7485(**
75- 一方、 `EvalInteraction` メソッドは結果を返しません。
76- このメソッドは画面出力機能であったり、F#の式としては不正なものの、
77- F# Interactiveコンソールには入力できるようなものなど、
78- 副作用を伴う命令を評価する場合に使用できます。
79- たとえば `#time "on"` (あるいはその他のディレクティブ)や `open System` 、
80- その他のトップレベルステートメントなどが該当します。
86+ これは引数に文字列を取り、それをF#コードとして評価(つまり実行)します。
8187*)
82- /// 命令を評価して、結果は無視する
83- let evalInteraction text =
84- fsiSession.EvalInteraction( text)
88+ evalExpression " 42+1" // '43' を表示する
89+
8590(**
86- これら2つのメソッドは文字列を引数にとり、
87- それをF#コードとして評価(あるいは実行)します。
91+ これは以下のように強く型付けされた方法で使うことができます:
92+ *)
93+
94+ /// 式を評価して、強く型付けされた結果を返す
95+ let evalExpressionTyped < 'T > ( text ) =
96+ match fsiSession.EvalExpression( text) with
97+ | Some value -> value.ReflectionValue |> unbox< 'T>
98+ | None -> failwith " 結果が得られませんでした!"
99+
100+ evalExpressionTyped< int> " 42+1" // '43' になる
101+
102+
103+ (**
104+ `EvalInteraction` メソッドは画面出力機能や宣言、
105+ F#の式としては不正なものの、F# Interactiveコンソールには入力できるようなものなど、
106+ 副作用を伴う命令を評価する場合に使用できます。
107+ たとえば `#time "on"` (あるいはその他のディレクティブ)や `open System` 、
108+ その他の宣言やトップレベルステートメントなどが該当します。
88109指定するコードの終端に `;;` を入力する必要はありません。
89110実行したいコードだけを入力します:
90111*)
91- evalExpression " 42+1 "
92- evalInteraction " printfn \" bye \" "
112+ fsiSession.EvalInteraction " printfn \" bye \" "
113+
93114
94115(**
95116`EvalScript` メソッドを使用すると、完全な .fsx スクリプトを評価することができます。
96117*)
97- /// スクリプトを評価して結果を無視する
98- let evalScript scriptPath =
99- fsiSession.EvalScript( scriptPath)
100118
101- evalScript " sample.fsx"
119+ File.WriteAllText( " sample.fsx" , " let twenty = 10 + 10" )
120+ fsiSession.EvalScript " sample.fsx"
121+
122+ (**
123+ 例外処理
124+ --------
125+
126+ コードに型チェックの警告やエラーがあった場合、または評価して例外で失敗した場合、
127+ `EvalExpression` 、 `EvalInteraction` そして `EvalScript` ではあまりうまく処理されません。
128+ これらのケースでは、 `EvalExpressionNonThrowing` 、 `EvalInteractionNonThrowing`
129+ そして `EvalScriptNonThrowing` を使うことが出来ます。
130+ これらは結果と `FSharpErrorInfo` 値の配列の組を返します。
131+ これらはエラーと警告を表します。結果の部分は実際の結果と例外のいずれかを表す
132+ `Choice<_,_>` です。
133+
134+ `EvalExpression` および `EvalExpressionNonThrowing` の結果部分は
135+ オプションの `FSharpValue` 値です。
136+ その値が存在しない場合、式が .NET オブジェクトとして表現できる具体的な結果を
137+ 持っていなかったということを指し示しています。
138+ この状況は実際には入力されたどんな通常の式に対しても発生すべきではなく、
139+ ライブラリ内で使われるプリミティブ値に対してのみ発生すべきです。
140+ *)
141+
142+ File.WriteAllText( " sample.fsx" , " let twenty = 'a' + 10.0" )
143+ let result , warnings = fsiSession.EvalScriptNonThrowing " sample.fsx"
144+
145+ // 結果を表示する
146+ match result with
147+ | Choice1Of2 () -> printfn " チェックと実行はOKでした"
148+ | Choice2Of2 exn -> printfn " 実行例外: %s " exn.Message
149+
150+
151+ (**
152+ は次のようになります:
153+
154+ 実行例外: Operation could not be completed due to earlier error
155+ *)
156+
157+ // エラーと警告を表示する
158+ for w in warnings do
159+ printfn " 警告 %s 場所 %d ,%d " w.Message w.StartLineAlternate w.StartColumn
160+
161+ (**
162+ は次のようになります:
163+
164+ 警告 The type 'float' does not match the type 'char' 場所 1,19
165+ 警告 The type 'float' does not match the type 'char' 場所 1,17
166+
167+ 式に対しては:
168+ *)
169+
170+
171+ let evalExpressionTyped2 < 'T > text =
172+ let res , warnings = fsiSession.EvalExpressionNonThrowing( text)
173+ for w in warnings do
174+ printfn " 警告 %s 場所 %d ,%d " w.Message w.StartLineAlternate w.StartColumn
175+ match res with
176+ | Choice1Of2 ( Some value) -> value.ReflectionValue |> unbox< 'T>
177+ | Choice1Of2 None -> failwith " null または結果がありません"
178+ | Choice2Of2 ( exn: exn) -> failwith ( sprintf " 例外 %s " exn.Message)
179+
180+ evalExpressionTyped2< int> " 42+1" // '43' になる
181+
182+
183+ (**
184+ 並列実行
185+ --------
186+
187+ デフォルトでは `EvalExpression` に渡したコードは即時実行されます。
188+ 並列に実行するために、タスクを開始する計算を投入します:
189+ *)
190+
191+ open System.Threading .Tasks
192+
193+ let sampleLongRunningExpr =
194+ """
195+ async {
196+ // 実行したいコード
197+ do System.Threading.Thread.Sleep 5000
198+ return 10
199+ }
200+ |> Async.StartAsTask"""
201+
202+ let task1 = evalExpressionTyped< Task< int>>( sampleLongRunningExpr)
203+ let task2 = evalExpressionTyped< Task< int>>( sampleLongRunningExpr)
204+
205+ (**
206+ 両方の計算がいま開始しました。結果を取得することが出来ます:
207+ *)
208+
209+
210+ task1.Result // 完了後に結果が出てくる (最大5秒)
211+ task2.Result // 完了後に結果が出てくる (最大5秒)
102212
103213(**
104214評価コンテキスト内での型チェック
@@ -109,7 +219,7 @@ F# Interactiveの一連のスクリプティングセッション中で
109219たとえばまず宣言を評価します:
110220*)
111221
112- evalInteraction " let xxx = 1 + 1"
222+ fsiSession.EvalInteraction " let xxx = 1 + 1"
113223
114224(**
115225
@@ -135,29 +245,54 @@ checkResults.Errors.Length // 1
135245*)
136246open Microsoft.FSharp .Compiler
137247
138- let identToken = Parser.tagOfToken ( Parser.token.IDENT ( " " ))
139- checkResults.GetToolTipTextAlternate( 1 , 2 , " xxx + xx" , [ " xxx" ], identToken ) // a tooltip
248+ // ツールチップを取得する
249+ checkResults.GetToolTipTextAlternate( 1 , 2 , " xxx + xx" , [ " xxx" ], FSharpTokenTag.IDENT )
140250
141- let symbolUse =
142- checkResults.GetSymbolUseAtLocation( 1 , 2 , " xxx + xx" , [ " xxx" ])
143- |> Async.RunSynchronously
251+ checkResults.GetSymbolUseAtLocation( 1 , 2 , " xxx + xx" , [ " xxx" ]) // シンボル xxx
144252
145253(**
146- 例外処理
147- --------
254+ 'fsi'オブジェクト
255+ -----------------
256+
257+ スクリプトのコードが'fsi'オブジェクトにアクセスできるようにしたい場合、
258+ このオブジェクトの実装を明示的に渡さなければなりません。
259+ 通常、FSharp.Compiler.Interactive.Settings.dll由来の1つが使われます。
260+ *)
261+
262+ let fsiConfig2 = FsiEvaluationSession.GetDefaultConfiguration( fsi)
263+
264+ (**
265+ 収集可能なコード生成
266+ --------------------
267+
268+ FsiEvaluationSessionを使用してコードを評価すると、
269+ .NET の動的アセンブリを生成し、他のリソースを使用します。
270+ `collectible=true` を渡すことで、生成されたコードを収集可能に出来ます。
271+ しかしながら、例えば `EvalExpression` から返される `FsiValue` のような型を必要とする未解放のオブジェクト参照が無く、
272+ かつ `FsiEvaluationSession` を破棄したに違いない場合に限ってコードが収集されます。
273+ [収集可能なアセンブリに対する制限](https://msdn.microsoft.com/ja-jp/library/dd554932%28v=vs.110%29.aspx#Anchor_1)
274+ も参照してください。
275+
276+ 以下の例は200個の評価セッションを生成しています。 `collectible=true` と `use session = ...`
277+ の両方を使っていることに気をつけてください。
278+
279+ 収集可能なコードが正しく動いた場合、全体としてのリソース使用量は
280+ 評価が進んでも線形には増加しないでしょう。
281+ *)
282+
283+ let collectionTest () =
284+
285+ for i in 1 .. 200 do
286+ let defaultArgs = [| " fsi.exe" ; " --noninteractive" ; " --nologo" ; " --gui-" |]
287+ use inStream = new StringReader( " " )
288+ use outStream = new StringWriter()
289+ use errStream = new StringWriter()
290+
291+ let fsiConfig = FsiEvaluationSession.GetDefaultConfiguration()
292+ use session = FsiEvaluationSession.Create( fsiConfig, defaultArgs, inStream, outStream, errStream, collectible= true )
293+
294+ session.EvalInteraction ( sprintf " type D = { v : int }" )
295+ let v = session.EvalExpression ( sprintf " { v = 42 * %d }" i)
296+ printfn " その %d , 結果 = %A " i v.Value.ReflectionValue
148297
149- コンパイルエラーをもっと洗練された形で処理して、
150- 使い勝手のよいエラーメッセージを出力させたい場合には
151- 以下のようにするとよいでしょう:
152- *)
153-
154- try
155- evalExpression " 42 + 1.0"
156- with e ->
157- match e.InnerException with
158- | null ->
159- printfn " 式 (%s ) の評価時にエラーが発生しました" e.Message
160- //| WrappedError(err, _) ->
161- // printfn "(ラップされた)式 (%s) の評価時にエラーが発生しました" err.Message
162- | _ ->
163- printfn " 式 (%s ) の評価時にエラーが発生しました" e.Message
298+ // collectionTest() <-- このようにテストを実行する
0 commit comments