ラむブラリパタヌンフレヌムワヌクが悪である理由

芪愛なる読者の皆さん、こんにちは



今日、技術蚘事の翻蚳を提䟛したいず思いたす。その著者であるThomas Petrichekは、Fでラむブラリを操䜜するさたざたな偎面を怜蚎しおいたす。 珟圚、著者が参加した1冊の本の可胜性を研究しおいるため、この蚘事は䞻にサンプルテキストずしお䜍眮付けられたす。この䟋では、著者の物語の才胜、圌のアむデアの質、議論、掚論、およびコヌド䟋を評䟡できたす。 ただし、この蚘事に蚘茉されおいる考慮事項はFでの䜜業に限定されるものではないため、この蚘事が幅広い読者にずっお有益で興味深いものになるこずを願っおいたす。





この蚘事は、機胜ラむブラリの蚭蚈に関する以前の投皿の 1぀に基づいお曞かれたしたが、別のトピックに専念しおいるため、このような玹介蚘事がなくおも非垞に理解しやすいでしょう。



前の蚘事で、関数型スタむルでラむブラリを䜜成する際に圹立぀いく぀かの原則を説明したした。 Fでラむブラリを䜜成する際の私自身の経隓に䟝存しおいたすが、ここで玹介するアむデアは非垞に普遍的であり、プログラミング蚀語を䜿甚する際に圹立ちたす。 以前の投皿で、耇数レベルの抜象化により、シナリオの80の実装を簡玠化するラむブラリを䜜成する方法を曞いたが、より興味深い実甚的なケヌスでも圹立ちたす。



この蚘事では、他の2぀の偎面に぀いお説明したす。構成可胜なラむブラリを開発する方法ず、ラむブラリを開発するずきにコヌルバックを回避する方法そしお最も重芁な理由です。 蚘事の名前が瀺すように、その本質は次のように芁玄されたすフレヌムワヌクではなくラむブラリを曞きたす

フレヌムワヌクずラむブラリの比范



フレヌムワヌクずラむブラリの違いは䜕ですか 䞻にそれらの䜿甚方法、および最初ず2番目の堎合のコヌドの性質はどうなりたすか。











䞊の図に違いを瀺したす。 フレヌムワヌクは、蚘入する必芁がある構造を定矩し、ラむブラリ自䜓には、コヌドを構築するための構造がありたす。



もちろん、ラむブラリずフレヌムワヌクぞのこのような分割は明確です。 䞀郚のコンポヌネントは、1番目ず2番目の機胜を組み合わせおいたす。ラむブラリなどのコンポヌネントを呌び出したすが、特定のニッチたずえば、むンタヌフェヌスを入力する必芁がありたす。



フレヌムワヌクの䜕が問題になっおいたすか



䞊蚘の図を確認するず、フレヌムワヌクでどのような問題が発生するかをすでに確認できたす。 このセクションでは、このような3぀の問題に関するいく぀かのこずを説明したす次のセクションでは、それらを解決する方法に぀いお説明したす。



フレヌムワヌクは構成したせん





おそらく、フレヌムワヌクの最倧か぀最も明癜な問題は、フレヌムワヌクが合わないこずです。 フレヌムワヌクが2぀ある堎合、それぞれに独自の特定のニッチがあり、それを埋める必芁がありたす。 しかし、通垞、あるフレヌムワヌクを別のフレヌムワヌクに挿入する方法はありたせんそしお、通垞、1぀のフレヌムワヌクが条件付きで内偎にあり、他のフレヌムワヌクが倖偎にある理由は明確ではありたせん。



ラむブラリの状況は異なりたす。 それらを管理するため、プログラムで倚くのラむブラリを呌び出すこずができたす。 もちろん、これにはいく぀かの困難が䌎いたす-ラむブラリの゚ンドポむントの呚りにより耇雑なコヌドを曞く必芁がありたす-しかし、原則ずしお、これは非垞に実行可胜です。







理論的埌退



私は、次の考慮事項に理論的根拠があるず䞻匵しおいたせんが、フレヌムワヌクはモナドに少し䌌おいたす。 モナドの倖にいる堎合は、モゞュヌルを䜿甚しおモナドを「䞭に入れる」こずができたす。 その埌、モナド内でさたざたな操䜜を実行できたすが、そこから抜け出すこずはできたせん。 フレヌムワヌクはこれらのモナドに䌌おいたす。

モナドの構成が難しいこずはよく知られおいたすフレヌムワヌクも同様。 モナドM1ずM2がある堎合、M1M2α→M2M1αの操䜜を䜿甚しおドッキングできたす。 包み蟌むモナドず包み蟌むモナドを入れ替えたす。 フレヌムワヌクでも同様のこずができたすか




フレヌムワヌクを探玢するのは難しいです。



フレヌムワヌクのもう1぀の倧きな問題は、フレヌムワヌクのテストず研究が難しいこずです。 Fでは、ラむブラリをFむンタラクティブ環境にロヌドし、さたざたな入力オプションで実行しおラむブラリの機胜を確認するず非垞に䟿利です。 たずえば、Suave Web開発ラむブラリを䜿甚しお、次のような簡単なWebサヌバヌを実行できたす。



//        #r "Suave.0.25.0/lib/net40/Suave.dll" open Suave.Web open Suave.Http //   -,     hello startWebServer defaultConfig <| fun ctx -> async { let whoOpt = ctx.request.queryParam "who" let message = sprintf "Hello %s" (defaultArg whoOpt "world") return! ctx |> Successful.OK message }
      
      







このフラグメントはラむブラリヌをロヌドしおから、デフォルトの構成ず芁求を凊理するための関数を䜿甚しおstartWebServerを呌び出したす関数はwho芁求パラメヌタヌを受け取り、グリヌティングを衚瀺したす。

ナヌザヌがラむブラリをすばやく詊すこずができるため、この方法は非垞に䟿利です。



さたざたなパラメヌタヌでstartWebServerを呌び出しお、それが䜕をするかたたは、他の関数の堎合は䜕を返すかを確認しおください。



理論的埌退



ラむブラリずフレヌムワヌクの違いは、関数呌び出しず匕数ずしお関数を指定する必芁性ずの間に存圚するものずよく䌌おいたす。

libτ1→τ2ラむブラリ

fwkσ2→σ1→単䜍フレヌムワヌク

ラむブラリの堎合、lib関数を呌び出すこずができるように、τ1の倀を䜜成する必芁がありたす。 ラむブラリは、τ1を䜜成する他の関数を提䟛する堎合がありたすこの堎合、このようなチェヌンから最初の関数を芋぀けお呌び出すだけです。 察話的にコヌドを蚘述する堎合、τ1の異なる倀を蚭定しお、関数を実行し、䜕が返されるかを確認できたす。 したがっお、ラむブラリの動䜜および必芁なものを実珟するためのラむブラリの䜿甚方法を簡単に調べるこずができたす。 さらに、この堎合、ラむブラリを䜿甚するコヌドのテストが簡玠化されたす。



フレヌムワヌクの堎合、より耇雑な状況が発生したす。 σ2を取り、σ1を生成する関数を䜜成する必芁がありたす。 最初の問題は、さたざたな状況でσ2の倀をどのように受け取るかを十分に理解しおいないこずです。 理想的な䞖界では、「無効な倀は衚珟できない」が、実際には、最も䞀般的なケヌスを䞻に凊理するコヌドの蚘述を開始したい。 望たしい動䜜を実珟するために、σ1のどの倀を䞎えるべきかを理解および調査するこずも難しくありたせん。




さお、Suaveの䟋に戻るず、読者は尋ねるかもしれたせん。それはラむブラリ関数を呌び出すかフレヌムワヌク呌び出すべき関数を瀺すなのでしょうか。 実際、䞊蚘の䟋は䞡方の偎面を瀺しおいたす。 埌で瀺すように、このような「フレヌムワヌク」構造のバリアントはそれほど悪くはありたせん以䞋のコヌルバックず非同期のセクションを参照しおください。



フレヌムワヌクは、コヌドの構成を決定したす。



フレヌムワヌクの次の問題は、フレヌムワヌクがコヌドの構造を決定するこずです。 そのような堎合の兞型的な䟋特定の基本クラスからの継承ず特定のメ゜ッドの実装を必芁ずするフレヌムワヌクを䜿甚しおいたす。 たずえば、XNAフレヌムワヌクのGameクラスは次のようになりたすXNAが死んでいるこずは知っおいたすが、このパタヌンは他の同様のフレヌムワヌクでも䜿甚されおいたす。



 class Game { abstract void Initialize(); abstract void Draw(DrawingContext ctx); abstract void Update(); }
      
      







初期化では、ゲヌムで必芁ずなる可胜性のあるリ゜ヌスをロヌドするこずを前提ずしおいたす。 次の状態を蚈算するためにUpdateが繰り返し呌び出され、画面を曎新する必芁があるずきにDrawが呌び出されたす。 むンタヌフェむスは、呜什型プログラミングモデルに明確に焊点を合わせおいるため、以䞋に瀺すこのコヌドフラグメントのようなものが埗られたす。 ここでは、「マリオ」でゲヌムの愚かなバヌゞョンを䜜成したす。マリオは、巊から右にゆっくりず移動したす。



 type MyGame() = inherit Xna.Game() let mutable x = 0 let mutable mario = None override this.Initialize() = mario <- Some(Image.Load("mario.png")) override this.Update() = x <- x + 1 override this.Draw(ctx) = mario |> Option.iter (fun mario -> ctx.Draw(x, 0, mario))
      
      







そのようなフレヌムワヌク構造は、その䞭に矎しいコヌドを曞くこずに貢献したせん。 ここでは、最も簡単な実装を可胜にしたした。 倉数フィヌルドxはマリオの堎所に察応し、マリオはリ゜ヌスを保存するためのオプション倀です。



Cでは、同様のコヌドがより矎しくなるず䞻匵するこずができたすたずえば、すべおのFフィヌルドを初期化する必芁があるため、オプション倀を䜿甚する必芁がありたしたが、これはチェックを完党に無芖する堎合にのみ圓おはたりたす。 実際、ここでオプション倀を䜿甚しお、コヌドをより安党にしたす初期化しおいないず誀っおDrawでマリオを䜿甚できないため。 たたは、フレヌムワヌクは、Drawの前にInitializeが垞に呌び出されるこずを保蚌したすか どうやっおそれを知るのですか



フレヌムワヌクの「匂い」を避ける方法



ラむブラリを䜜成する方がフレヌムワヌクを䜜成するよりも優れおいるず玍埗できたこずを願っおいたす。 しかし、これたでのずころ、これがどのように行われるかに぀いお、具䜓的なアドバむスはしおいたせん。 この蚘事の残りの郚分では、いく぀かの特定の䟋を怜蚎したす。



むンタラクティブな研究をサポヌト



Fでラむブラリを䜜成しおいない堎合でも、FInteractiveを䜿甚しお、むンタラクティブに䜿甚できるようにする必芁がありたす。 ポむントは、F蚀語がラむブラリのドキュメント化に完党に適合しおいるだけでなく、むンタラクティブスクリプトを蚘述するこずで、ラむブラリを呌び出すのが非垞に簡単になるこずを確認できたす.NETプラットフォヌムで䜜業しおいる堎合は、オプションはLINQPadず連携するこずです。



2぀の䟋を䜿甚しお、掚論を説明したす。 最初のコヌドスニペットは、 FFormattingラむブラリを䜿甚しお、FスクリプトファむルずMarkdownドキュメントを含むドキュメントディレクトリをHTMLファむルに倉換する方法、たたは別のファむルを凊理する方法を瀺しおいたす。



 #r "FSharp.Literate.dll" open FSharp.Literate //    Literate.ProcessDirectory("C:/demo/docs") //     Literate.ProcessMarkdown("C:/demo/docs/sample.md") Literate.ProcessScriptFile("C:/demo/docs/sample.fsx")
      
      







ポむントは、ラむブラリを参照し、名前空間を開き、゚ントリポむントずしおLiterateタむプを芋぀ける必芁があるこずです。 これにより、「。」を䜿甚できたす。 そしおあなたが持っおいるものを参照しおください



すべおの優れたラむブラリがこのプラクティスをサポヌトするはずだず思いたす。 別の䟋ずしお、FコヌドをJavaScriptに倉換するFunScriptを芋おみたしょう。 原則ずしお、Webフレヌムワヌクの䞀郚ずしお䜿甚されたすが、それ自䜓で正垞に機胜したす。 次のスニペットは、単玔な非同期ルヌプ甚のJavaScriptコヌドを生成したす。このルヌプは、ペヌゞごずの数を1぀増やしたす。



 #r "FunScript.dll" #r "FunScript.TypeScript.Binding.lib.dll" open FunScript open FunScript.TypeScript Compiler.compile <@ let rec loop n : Async<unit> = async { Globals.window.document.title <- string n do! Async.Sleep(1000) return! loop (n + 1) } loop 0 @>
      
      







繰り返したすが、ラむブラリこの堎合はDOMバむンディングを参照し、1぀の関数を呌び出したす。コンパむル関数はF匕甚笊を受け入れたす。 これを発芋したら、あなたはそれを自分で詊すこずができたす、それは䜕を扱うこずができたす 前の䟋は、Fasync {...}の矎しいサポヌトず、DOMぞのアクセスを可胜にするバむンディングを瀺しおいたす。



単玔なコヌルバックのみを䜿甚する



䞊蚘の理論的䜙談でフレヌムワヌクに぀いお話したずき、本質的に、関数を匕数ずしお取る構造はフレヌムワヌクず呌ばれるこずに泚意したした。 高階関数を䜿甚すべきではないずいうこずですか もちろん違いたす



次の2぀の単玔なフラグメントを比范したす。1぀目はリストの凊理に暙準関数を䜿甚し、2぀目は特定の入力を読み取り1぀目の関数を䜿甚、次に怜蚌および凊理2぀目の関数を䜿甚したす。



 //      [ 1 .. 10 ] |> List.filter (fun n -> n%3 = 0) |> List.map (fun n -> n*10) //      ,  , //          readAndProcess (fun () -> File.ReadAllText("C:/demo.txt")) (fun s -> s.ToUpper())
      
      







最初の䟋ず2番目の䟋には2぀の違いがありたす。 リスト凊理関数を䜿甚する堎合、匕数ずしお垞に1぀の関数のみを指定したす。 さらに、そのような関数は決しおステヌトフルであっおはなりたせん。



2番目の堎合、2぀の機胜が瀺されたす。 私の意芋では、これは関数が必芁以䞊に耇雑であるこずが刀明した兆候です。



次に、readAndProcessは、最初の関数から文字列の状態を返し、2番目の関数ぞの入力ずしお文字列を受け入れるようにしたす。 これは別の朜圚的な問題です。 最初の関数から2番目の関数に他の状態を転送する必芁がある堎合はどうなりたすか



もちろん、ここでは簡単なケヌスを怜蚎しおいたすが、readAndProcessの内郚で䜕が起こるか芋おみたしょう。 この関数はいく぀かの䟋倖を凊理し、最初に入力の有効性をチェックし、その埌で2番目の匕数を呌び出したす。



 let readAndProcess readInput processInput = try let input = readInput() if input = null || input = "" then None else Some(processInput input) with :? System.IO.IOException -> None
      
      







この抜象化はどのように改善できたすか たず、この関数は実際に2぀の問題を解決したす。 たず、䟋倖を凊理したすかなり愚かですが、これはケヌススタディです。 次に、入力を怜蚌したす。 これを2぀の機胜に分割できたす。



 let ignoreIOErrors f = try Some (f()) with :? System.IO.IOException -> None let validateInput input = if input = null || input = "" then None else Some(input)
      
      







珟圚、validateInputは、入力が有効な堎合にSomeを返す最も䞀般的な関数になりたす。 ignoreIOErrors関数はただ関数を匕数ずしお䜿甚したす-䟋倖凊理は䞭間パタヌンのホヌルの兞型的な䟋であるため、この堎合はお勧めです。 あなたが曞くこずができる新しい関数を䜿甚しお



 ignoreIOErrors (fun () -> let input = File.ReadAllText("C:/demo.txt") validateInput input |> Option.map (fun valid -> valid.ToUpper() ))
      
      







詊しおみるず、ここで3行を満たすこずができたすが、コヌドは少し長くなり、少し明確になりたす。



私の意芋では、これはプラスです、䜕が起こっおいるのかがわかるのでそしお、validateInputのむンタラクティブな呌び出しから始めるこずができたすさらに、readAndProcess関数を奜む堎合、これも良いです-䞊蚘の2぀の関数を䜿甚しお簡単に決定できたすその逆ではありたせんしたがっお、ラむブラリはマルチレベルの抜象化を提䟛できたす。 これに぀いおは、前の蚘事で説明したした。 しかし、高レベルの抜象化のみを提䟛する堎合、これは可胜性を制限したす。



芁玄するず、匕数ずしお関数を枡すこずは可胜ですが、泚意が必芁です。 関数が匕数ずしお2぀以䞊の関数を取る堎合、これはおそらく最高の䜎レベルの抜象化ではありたせん。 匕数ずしお報告される関数が分離しお䜕らかの状態を枡す必芁がある堎合は、確実に代替手段を提䟛する必芁がありたす呌び出し偎が「暙準」状態転送ではなく、䜕か他のものを必芁ずする堎合。



むベントず非同期を䜿甚しおコヌルバックを反転する



フレヌムワヌクがコヌドの構成にどのように圱響するかに぀いお、簡単なゲヌム゚ンゞンを䟋に挙げたした。 可倉フィヌルドを䜿甚しお特定のクラスを実装する必芁がないように、別の方法で䜕ができるでしょうか Fでは、非同期ワヌクフロヌずむベント駆動型プログラミングモデルを䜿甚できたす。



状況は、蚈算匏およびそのような機胜をシミュレヌトできるむテレヌタのような蚀語では耇雑ですが、Cはawaitをサポヌトし、FはHaskellで蚈算匏を持ちたす-衚蚘法を実行し、おそらくPythonで悪甚できたすゞェネレヌタヌ。



実装する必芁がある仮想メ゜ッドを蚘述する代わりに、操䜜が必芁なずきに起動するむベントを提䟛するずいう考え方です。 したがっお、Gameクラスのむンタヌフェむスは次のようになりたす。



 type Game = member Update : IEvent<unit> member Draw : IEvent<DrawingContext> member IsRunning : bool
      
      







Fasyncを䜿甚する堎合、異なる方法でコヌドを䜜成できたす。 フレヌムワヌクずラむブラリを比范するずいう元の前提に戻るず、発生するすべおを完党に制埡できたす。 次の䟋では、リ゜ヌスずGameオブゞェクトを初期化し、AwaitObservableメ゜ッドを䜿甚しおUpdateたたはDrawむベントを埅機するルヌプ再垰的な非同期ブロックを䜿甚を実装したす。



 //     let game = Inverted.Game() let mario = Image.Load("mario.png") //  ,      let rec loop x = async { if game.IsRunning then let! evt = Async.AwaitObservable(game.Update, game.Draw) match evt with | Choice1Of2() -> //   'Update' return! loop (x + 1) | Choice2Of2(ctx) -> //   'Draw' ctx.Draw(x, 0, mario) return! loop x } //  Game  x=0 loop 0
      
      







もちろん、ゲヌムの状態を曎新したり、画面を再描画したりするためにシステムからい぀電話を受け取るかわからないため、絶察的な制埡を実珟するこずはできたせん。 ただし、リ゜ヌスの初期化を完党に制埡し、ゲヌムの進行状況を確認しお、1぀たたは別のむベントを埅぀こずができたす。



ここで重芁なのは、非同期{...}の䜿甚です。 AwaitObservableを䜿甚しお、「UpdateたたはDrawが必芁なずきにコンピュヌティングを再開したす」を泚文できたす。 むベントが発生するず、必芁なアクション行12で状態を曎新するか、行15でマリオを描くを実行しおから続行したす。 この堎合の最倧の利点は、そのようなコヌドを簡単に拡匵しお、より耇雑なロゞックを提䟛できるこずです。たずえば、Phil Trelfordの蚘事を参照しおください。 これらのプロパティを実装する別のオプションは、 F゚ヌゞェントを䜿甚するこずです。これにより、ロゞックを同様に制埡できたす。



だから、今私たちはコントロヌルされおいたすが、私たちは倚くを達成したしたか Fに慣れおいない堎合、䞊蚘のコヌドは混乱を招く可胜性がありたす。 䞻なアむデアは、制埡を逆にするこずで、独自の抜象化を簡単に䜜成できるずいうこずです。 ここで最終段階に到達したす...



耇数レベルの抜象化を䜿甚する



以前の投皿で曞いたように、ラむブラリはいく぀かのレベルの抜象化を提䟛する必芁がありたす。 前のスニペットで䜿甚したゲヌムのタむプは、䜎レベルの抜象化です。 完党な制埡を提䟛しながら、耇雑なものを䜜成する堎合に圹立ちたす。 しかし、他の堎合では、ゲヌムは実際には「曎新」ず「描画」の2぀の機胜で構成されたす。

前のコヌドフラグメントを取埗しお、匕数にいく぀かの郚分を抜出するだけなので、これは問題なく行われたす。



 let startGame draw update init = let game = Inverted.Game() let rec loop x = async { if game.IsRunning then let! evt = Async.AwaitObservable(game.Update, game.Draw) match evt with | Choice1Of2() -> return! loop (update x) | Choice2Of2(ctx) -> draw x ctx return! loop x } loop init
      
      







抜象化startGameは、匕数ずしお2぀の関数ず初期状態を取りたす。 曎新関数は状態を曎新し、描画関数は指定されたDrawingContextを䜿甚しお状態を描画したす。 したがっお、マリオでのゲヌムをわずか4行で蚘録できたす。



 let mario = Image.Load("mario.png") 0 |> startGame (fun x ctx -> ctx.Draw(x, 0, mario)) (fun x -> x + 1)
      
      







投皿党䜓を泚意深く読んだ堎合、次のように尋ねるこずができたす。 䞊蚘で、耇数の関数を受け入れる高次関数特に状態を共有しおいる堎合は悪質なフレヌムワヌクであるず曞いおいたせんか はい、そう蚀いたした しかし、この点を明確にしたしょう。



他のいく぀かの関数を高レベルの抜象化ずしお䜿甚する䟿利な操䜜を行うこずは非垞に可胜ですが、より単玔で明確な代替手段も必芁です。



䞊蚘の4行を䜿甚しお、startGameの定矩を芋お、それらを既に䞊で芋た14行のコヌドに倉換できたすコメントを陀く。 ぀たり、ボンネットの䞋に1぀あたり深くないのステヌゞを配眮するこずで、コントロヌルを獲埗できるはずです。 この方法は、うたく機胜しおいないラむブラリの䞊に壊れやすい足堎を構築するこずずは異なりたす。ラむブラリは、矎しいコヌドを曞くこずに頌らなければならない堎合がありたす。



リンク可胜なラむブラリを蚭蚈する



前述したように、フレヌムワヌクではなくラむブラリを䜜成する䞻な理由の1぀は、ラむブラリのコンパむルが原因です。 このプロセスを完党に制埡できる堎合は、問題のどの郚分を解決するために䜿甚するラむブラリを遞択できたす。 難しい堎合もありたすが、ラむブラリを䜿甚すれば、少なくずもチャンスがありたす。



うたくドッキングするラむブラリを䜜成するための普遍的なレシピはないず思いたす。 次のこずに泚意するこずがおそらく重芁ですタむプは、同様のデヌタ構造を䜜成する必芁がある堎合、他のラむブラリが同様の目的で必芁ずするすべおの重芁な情報を提䟛する必芁がありたす。



この皮の良い䟋はFsLabです。これは、デヌタを操䜜するための倚数のFパッケヌゞ Deedle 、 Math.NET Numericsなどを含むを組み合わせたパッケヌゞです。 FsLabパッケヌゞには、他の倚くのラむブラリをリンクする1぀のスクリプトが付属しおいたす゜ヌスコヌドはこちら 。



ファむルからの2぀の簡単な䟋は、マトリックスからフレヌムMatrix.toFrameに、反察方向Frame.toMatrixに倉換する関数です。



 module Matrix = let inline toFrame matrix = matrix |> Matrix.toArray2 |> Frame.ofArray2D module Frame = let inline toMatrix frame = frame |> Frame.toArray2D |> DenseMatrix.ofArray2
      
      







DeedleフレヌムずMath.NETマトリックスの䞡方を2次元配列に、たたはその逆に倉換できるため、ここでの解決策は非垞に簡単です。したがっお、配列内のある芁玠から別の芁玠に移動するだけです。



それは非垞に単玔に芋えたすが、私は次の本質を芋おいたすあなたのラむブラリが䜕をしおいおも、あなたはこのラむブラリを他のものず䞀緒に保぀ためにあらゆる努力を払うべきです



All Articles