FでDSLを作成する#

Code Projectの記事の翻訳をコミュニティに紹介します。この翻訳では、F#言語を使用してDSLを作成するプロセスを説明しています。









はじめに







正直に言うと、私はDSLについて純粋に学術的な方法で話すことにかなりうんざりしています。 この幸福が「生産」でどのように使用されているか、具体的な例を見てみたい。 とにかく、コンセプト自体は、オスロやMPSのようなフレームワークの作者よりもはるかにわかりやすく簡単に説明および実装できます。 実際、ここでは、アカデミックなものではなく、つまり生産であり、特定の目的に役立つソリューションを示したいだけです。









DSLとは何かを議論することから始めましょう。 DSLは、ドメイン固有の言語です。つまり、開発者だけでなく、主題分野の専門家も理解できる言語を使用して、特定の主題固有性(多くの場合、特定の業界に関連付けられています)を記述する方法です。 この言語で重要なことは、それを使用する人は中括弧、セミコロン、およびその他のプログラミングの喜びを考えてはならないということです。 つまり、彼らは「平易な英語」(ロシア語、日本語など)で書くことができるはずです。









このエッセイでは、F#言語を使用してDSLを記述し、プロジェクトの複雑さを評価するのに役立ちます。 このDSLinaのより難解なバージョンが、本番環境で使用されています。 私がすぐに示すコードはF#を使用する理想的な例とはほど遠いので、プログラミングスタイルの観点から「庭の石」をすべて無視します。 これはポイントではありません。 ただし、最適化を希望する場合は、お願いします。









そうそう、もう1つ- 元の記事ソースコードへの参照をすぐに提供します 。 コードは基本的に1つの.fs



ファイルです。 コンパイルできるといいのですが。 どのように機能するかを評価するには、Project 2007が必要です。それがない場合は、近くのPMにお問い合わせください。









じゃあ行って!









問題の説明







誰かがカスタムソフトウェアを必要とするとき、この誰か(通常「顧客」と呼ばれます)は、いわゆるRFP(提案の要求)をさまざまな企業に送信します。つまり、本質的にはプロジェクトの説明です。 この要求に対して、開発者はプロジェクト計画を作成し(十分な情報がある場合-コミュニケーションを開始します)、それを美しいPDFにパックして送り返します。もちろん、見積り(見積り)が迅速に行われるほど、より適切で、より適切に提示されます。クライアントがあなたと通信する可能性が高くなります。 この見積もりを適切かつ迅速に行うことは、会社全体の利益であることが判明しています。









誰かがこの見積もりを行う必要があります...通常、「極端な」ものは、​​音楽にリラックスしたPMであり、テクノロジースタックを十分に知っており、少なくとも少し経験を積んでいます(修正された場合、ピアレビューメカニズムはとにかくスムーズになりますすべてのジャム)。 したがって、RMはプロジェクトの段階を評価し、美しいタイムライン(GANTTチャートと呼ばれるようです)を作成して、開発者、テスター、および私たちの努力がどうなるかを明確に示す必要があります。 問題があります。









問題は、この幸福感を生み出すツールであるMS Projectが、絶えず評価の再構築、タスクの変更、リソースの変更、オーバーヘッドなどを必要とする場合、すぐに登れないことです。 特に、「各クライアントは自分のタイムゾーンから1日以内に見積もりを受け取る必要がある」というルールを順守している場合、すべてがストレスになりすぎます。 回避する必要があり、DSLはすべての参加者の評価活動を簡素化し、スピードアップする試みです。









言語選択







問題について説明しましたが、解決策について説明しました。 原則として、プロジェクトを説明するために、任意の構文を使用できる「無料の」DSLを作成し、スマートフレームワークを使用して解析することができますが、これらのフレームワークが結果に何も追加しないと考えるとつまらないものになりますが、確かに少し頭痛の種になります。 したがって、より簡単なアプローチは、「ほぼ英語」で記述できる言語(この場合は.Netスタックの言語)を選択することであり、技術者以外の人に大きな負担をかけることはありません(ただし、RMがプログラミングできない場合は、これは私たちの目的ではありません)。









もちろん、DSLで人気のある言語の中で、Booに注目する必要があります。これは彼の本でAyendeを非常によく推進しました 。 Booは非常に強力な言語ですが、この場合、そのメタプログラミング機能は必要ありません。 DSLの点でも人気のあるRuby言語はまだありますが、残念ながら私はそれに慣れていない(残念な省略)のでお勧めできません。 さて、私がやめた最後の選択肢はF#です。









F#がDSLに適しているのはなぜですか? その構文は心に負担をかけないからです。 ほとんど純粋な英語で書くことができます。 DSLは誰でも読むことができます。 唯一の問題は、F#が変数の不変性に焦点を合わせていることです。そのため、このコンテキストでは、その構造の一部が少し不自然に見えます。 しかし、私が言ったように、それはポイントではありません-DSLは単なる変圧器であるため、「意識を伝える」。









最初の声明







簡単なものから始めましょう。 プロジェクトの説明の最初の行は次のとおりです。









project "Write F# DSL Article" starts_on "16/8/2009" <br/>







上記の内容は、F#で完全に正当な表現です。 project



メソッドを呼び出して、 project



の名前、特定のトークン(アングロシンタクティックシュガーとして機能するダミー)、プロジェクトの開始時刻という3つのパラメーターを渡すだけです。 実際、私たちはBDDでテストを行うのとほぼ同じことを行っています。つまり、技術者以外が読めるようにしています。









単独で記述するDSLはOERに基づいています。 私たちの目標は、PMが使用されるすべての設計をサポートするDSLを介することです。 これらの構造の1つはプロジェクトなので、それから始めましょう。









type Project() =<br/>

[<DefaultValue>] val mutable Name : string<br/>

[<DefaultValue>] val mutable Resources : Resource list<br/>

[<DefaultValue>] val mutable StartDate : DateTime<br/>

[<DefaultValue>] val mutable Groups : Group list<br/>







さて、可変サポートを使用して記述した場合、F#はそれほどシックに見えないことを警告しました。 上記の奇妙な構造は、変更可能なパブリックフィールドです。 コレクションに関しては、 System.Collections.Generic



List<T>



代わりにF# list



を使用しました。 特別な違いはありません。









C#とは異なり、F#には、一見「グローバルスコープ」と呼ばれるものがあります。つまり、明示的に記述されたクラス、モジュール、名前空間なしで、変数と関数を「トップレベル」で宣言できます。 これをすぐに活用しましょう。









<br/>

let mutable my_project = new Project()<br/>







「デフォルトのプロジェクト変数」を作成しました。 当然、F#の用語は若干異なりますが、ポイントではありません。 最終的にprepare my_project



プロジェクト計画の自動prepare my_project



開始できるように、名前を選択しました。 それまでの間、すべてが始まるproject



関数を見てみましょう。









<br/>

let project name startskey start =<br/>

my_project <- new Project()<br/>

my_project.Name <- name<br/>

my_project.Resources <- []<br/>

my_project.Groups <- []<br/>

my_project.StartDate <- DateTime.Parse(start)<br/>







よくここに。 原則として、この段階で記事を読むのを安全にやめて実験することができます-結局のところ、F#でDSLを作成する本質を示しただけです。 次に、セマンティクスの分析と、実際にさまざまな微妙さがどのように解決されるかを示します。









リストを操作する







プロジェクトでの作業は、リソース、つまり人によって行われます。 あなたはリソースであり、私はリソースです-あまり良くありませんよね? それでも、各リソースには特定のタイトル(たとえば、ジュニア開発者)、名前(「John」)、およびレート(このリソースの作業に対して毎月会社が受け取りたい1時間あたりのドル数)があります。 最初に、このリソース自体の定義を見てみましょう。









type Resource() =<br/>

[<DefaultValue>] val mutable Name : string<br/>

[<DefaultValue>] val mutable Position : string<br/>

[<DefaultValue>] val mutable Rate : int<br/>







これで、DSLでのリソース作成がどのようになるかを見ることができます。









resource "John" isa "Junior Developer" with_rate 55<br/>







もちろん、上記の表現をサポートするために、プロジェクトと同じシャーマニズムを使用します。









let resource name isakey position ratekey rate =<br/>

let r = new Resource()<br/>

r.Name <- name<br/>

r.Position <- position<br/>

r.Rate <- rate<br/>

my_project.Resources <- r :: my_project.Resources<br/>







ご想像のとおり、リソースを作成しリストの先頭に追加します。 これは、リストに保存されているリソースやその他の要素を「構築」するときが来ると、各リストを前後に展開する必要があることを意味します。 これは私にとって問題ではありませんが、気に入らない場合はList<T>



使用してください。









文字列参照







DSLの次の概念はタスクグループです。 通常、プロジェクト内のタスクのグループは1人で実行されます。これにより、「認知の焦点」を維持できます。 このようなグループを定義します:









group "Project Coordination" done_by "Dmitri" <br/>







そして、これはグループに関するデータを含むオブジェクトです:









type Group() =<br/>

[<DefaultValue>] val mutable Name : string<br/>

[<DefaultValue>] val mutable Person : Resource<br/>

[<DefaultValue>] val mutable Tasks : Task list<br/>







ご覧のとおり、グループはタイプResource



オブジェクトを参照し、名前(文字列)を渡します。 ただし、リスト内の検索をキャンセルした人はいないため、これは問題ではありません。









let group name donebytoken resource =<br/>

let g = new Group()<br/>

g.Name <- name<br/>

g.Person <- my_project.Resources |> List.find( fun f -> f.Name = resource)<br/>

<br/>

my_project.Groups <- g :: my_project.Groups<br/>







LINQとは異なり、検索結果を取得するためにSingle()



を呼び出す必要はありません。









より柔軟に







タスクのグループ(タスク)は、ummのタスクで構成されます。 そして、タスクは次のように定義するのは悪くありません:









task "PayPal Integration" takes 2 weeks<br/>







これはF#でも本物です! まず、「砂糖」に通常使用するトークンに異なる値が含まれていることを確認します。









let hours = 1<br/>

let hour = 1<br/>

let days = 2<br/>

let day = 2<br/>

let weeks = 3<br/>

let week = 3<br/>

let months = 4<br/>

let month = 4<br/>







これでTask



を定義できます:









type Task() =<br/>

[<DefaultValue>] val mutable Name : string<br/>

[<DefaultValue>] val mutable Duration : string<br/>







グループにタスクを追加すると次のようになります。









let task name takestoken count timeunit =<br/>

let t = new Task()<br/>

t.Name <- name<br/>

let dummy = 1 + count<br/>

<br/>

match timeunit with <br/>

| 1 -> t.Duration <- String.Format( "{0}h" , count)<br/>

| 2 -> t.Duration <- String.Format( "{0}d" , count)<br/>

| 3 -> t.Duration <- String.Format( "{0}wk" , count)<br/>

| 4 -> t.Duration <- String.Format( "{0}mon" , count)<br/>

| _ -> raise(ArgumentException( "only spans of hour(s), day(s), week(s) and month(s) are supported" ))<br/>

<br/>

let g = List.hd my_project.Groups<br/>

g.Tasks <- t :: g.Tasks<br/>







上記のコードでは、時定数に応じてタスクの期間を調整します。 タスクを追加するグループを見つけるために、 List.hd



を使用しList.hd



-グループも後方にあるためです。









プロジェクトの自動生成







さて、それだけです! これで、1つの豪華なコマンドを呼び出してプロジェクト計画を生成できます。









prepare my_project<br/>







次に難しいのは、OfficeオートメーションとF#を組み合わせて使用​​して、DSLから計画を生成することです。 何が起こっているのかが明確になるように、コードにコメントしようとしました。









let prepare (proj:Project) =<br/>

let app = new ApplicationClass()<br/>

app.Visible <- true <br/>

let p = app.Projects.Add()<br/>

p.Name <- proj.Name<br/>

<br/>

proj.Resources |> List.iter( fun r -><br/>

let r2 = p.Resources.Add()<br/>

r2.Name <- r.Position // position, not name :)

let tables = r2.CostRateTables<br/>

let table = tables.[1]<br/>

table.PayRates.[1].StandardRate <- r.Rate<br/>

table.PayRates.[1].OvertimeRate <- (r.Rate + (r.Rate >>> 1)))<br/>

<br/>

let root = p.Tasks.Add()<br/>

root.Name <- proj.Name<br/>

<br/>

proj.Groups |> List.rev |> List.iter( fun g -> <br/>

let t = p.Tasks.Add()<br/>

t.Name <- g.Name<br/>

t.OutlineLevel <- 2s<br/>

<br/>

t.ResourceNames <- g.Person.Position<br/>

<br/>

let tasksInOrder = g.Tasks |> List.rev<br/>

tasksInOrder |> List.iter( fun t2 -><br/>

let t3 = p.Tasks.Add(t2.Name)<br/>

t3.Duration <- t2.Duration<br/>

t3.OutlineLevel <- 3s<br/>

<br/>

let idx = tasksInOrder |> List.findIndex( fun f -> f.Equals(t2))<br/>

if (idx > 0) then <br/>

t3.Predecessors <- Convert.ToString(t3.Index - 1)<br/>

)<br/>

)<br/>







まあ、 List.rev



を使用してリストを「拡張」しList.rev



-最速の操作ではありませんが、それは重要ではありません。 主なものは、スクリプトが機能し、プロジェクトを生成することです-それは、リソース、タスクグループ、およびタスク自体を定義します。 RMには他に何が必要ですか? (実際には多くのこと:)









そして、DSLを使用した完全なプロジェクトの説明は次のようになります。









project "F# DSL Article" starts "01/01/2009" <br/>

resource "Dmitri" isa "Writer" with_rate 140<br/>

resource "Computer" isa "Dumb Machine" with_rate 0<br/>

group "DSL Popularization" done_by "Dmitri" <br/>

task "Create basic estimation DSL" takes 1 day<br/>

task "Write article" takes 1 day<br/>

task "Post article and wait for comments" takes 1 week<br/>

group "Infrastructure Support" done_by "Computer" <br/>

task "Provide VS2010 and MS Project" takes 1 day<br/>

task "Download and deploy TypograFix" takes 1 day<br/>

task "Sit idly while owner waits for comments" takes 1 week<br/>

prepare my_project<br/>







おわりに







このエッセイで、F#でのDSLの作成が簡単であることを示していただければ幸いです。 もちろん、私が上で引用した例は、実際に使用しているものに比べて単純化されています。 しかし、これらは、彼らが言うように、会社の秘密です。 じゃあね!








All Articles