面向組合子(Combanitor-Oriented),是最近幫我打開(kāi)新世界大門(mén)的一種pattern。緣起haskell,又見(jiàn)monad與ParseC,終于ajoo前輩的幾篇文章。自去年9月起正式回歸C#以來(lái),我又逐漸接受了不少新的paradigm(雖然主要原因還是在學(xué)校用C#的方法太山寨),其中對(duì)我影響比較深刻的就是codegen。此codegen非compiler中的codegen,可能更像是meta-PRogramming中的codegen。抽象來(lái)說(shuō),就是作為一個(gè)嵌入于構(gòu)建流程中的某一步驟,拿到一些元描述信息,來(lái)生成代碼。我目前所接觸到的codegen的具體應(yīng)用情景,有這樣幾種:1.RPC相關(guān)的,數(shù)據(jù)打解包邏輯、Stub/Skeleton、組播等2.配表轉(zhuǎn)代碼3.策劃配出來(lái)的可視化行為樹(shù)轉(zhuǎn)代碼從這些情景可以看出這種需求的典型特征:性能好、便于上層調(diào)用。
具體來(lái)說(shuō),我們還是拿這種形式跟一些比較傳統(tǒng)的形式做下對(duì)比:RPC打解包邏輯直接自動(dòng)走函數(shù) V.S. protobufcodegen成C#代碼的行為樹(shù) V.S. 硬解腳本C#結(jié)構(gòu)描述的配置 V.S. 一坨meta二進(jìn)制+一坨data二進(jìn)制又是一堆廢話,現(xiàn)在直接進(jìn)入主題。
首先定義一個(gè)概念,Coder,當(dāng)然這跟平時(shí)一些低端討論串上經(jīng)常引起的Coder還是Programmer中的Coder沒(méi)關(guān)系,這里我們把它理解為一個(gè)函數(shù),接收一個(gè)T描述結(jié)構(gòu)作為參數(shù),輸出一個(gè)字符串。為了更C#一點(diǎn),我們這樣定義Coder:
public interface ICoder<in T> { string Code(T meta); }這是所有Coder的基本表現(xiàn)形式,與之對(duì)應(yīng)的,任何復(fù)雜的代碼生成程序,其實(shí)本質(zhì)都是通過(guò)一個(gè)抽象數(shù)據(jù)結(jié)構(gòu)生成一個(gè)字符串。基于ICoder,我們先從最簡(jiǎn)單的組合子開(kāi)始構(gòu)造,也就是"0"和"1":
internal class UnitCoder<T> : ICoder<T> { readonly string output; public UnitCoder(string output) { this.output = output; } public override string Code(T meta) { return output; } } internal class ZeroCoder<T> : ICoder<T> { private static ZeroCoder<T> instance; public static ZeroCoder<T> Instance { get { return instance ?? (instance = new ZeroCoder<T>()); } } public override string Code(T meta) { return ""; } }UnitCoder:不論給什么作為輸入,都只返回一個(gè)固定的字符串ZeroCoder:不論給什么作為輸入,都返回空字符串只有這兩個(gè)的話,似乎還是什么都不能做,我們需要一個(gè)最基本的可以讓我們定制的Coder:
internal class BasicCoder<T> : ICoder<T> { private readonly Func<T, string> func; public BasicCoder(Func<T, string> func) { this.func = func; } public override string Code(T meta) { return func(meta); } }假設(shè)現(xiàn)在有一個(gè)結(jié)構(gòu)定義:
class Meta1 { public string Type; public string Name; public string Value; }如此構(gòu)造一個(gè)BasicCoder:
var basicCoder = Generator.GenBasic((Meta1 m) => string.Format(@"{0} {1} = {2}", m.Type, m.Name, m.Value));這樣,通過(guò)給basicCoder傳不同的、具體的Meta1實(shí)例,這個(gè)Coder就跟真的Coder一樣coding出了不一樣的代碼。僅有這三個(gè)還不夠,我們還需要想一種辦法將兩個(gè)Coder組合起來(lái)。說(shuō)實(shí)話,這一塊代碼我寫(xiě)得非常丑,整理成博客的原因也是希望有哪位前輩看到能指點(diǎn)一下。好了,直接上有很明顯bad smell的代碼。首先需要對(duì)最基本的ICoder結(jié)構(gòu)進(jìn)行改造:
public interface ICoder { string Code(object meta); } public interface ICoder<in T> : ICoder { string Code(T meta); }這樣ICoder來(lái)提供通用的Coder接口,方便后面的SequenceCoder。所有的Coder都復(fù)用一下這樣的邏輯:
internal abstract class CoderBase<T> : ICoder<T> { private readonly T instance; public abstract string Code(T meta); public string Code(object meta) { if (meta is T) { return Code((T)meta); } throw new Exception("..."); } }然后我們著手實(shí)現(xiàn)SequenceCoder:
internal class SequenceCoder<T> : CoderBase<T> { readonly ICoder[] coderArr; readonly Func<T, ICoder[], string> coderJoiner; public SequenceCoder(ICoder[] coderArr, Func<T, ICoder[], string> coderJoiner) { this.coderArr = coderArr; this.coderJoiner = coderJoiner; } public override string Code(T meta) { return coderJoiner(meta, coderArr); } }我對(duì)SequenceCoder的定位是,Coder組合子系統(tǒng)內(nèi)部的一個(gè)結(jié)合不同Coder的基礎(chǔ)組件。有了SequenceCoder,我們就可以多出來(lái)很多有意義的東西了。之前我們構(gòu)造的basicCoder,是沒(méi)打出來(lái)語(yǔ)句末尾的";"的,我們來(lái)構(gòu)造一下。先是前后綴的一些公共邏輯:
internal static ICoder<T> WithPostfix<T>(this ICoder<T> coder, string postfix) { var coderPostfix = new UnitCoder<T>(postfix); return new SequenceCoder<T>(new ICoder[] { coder, coderPostfix }, (meta, arr) => string.Join("", coder.Code(meta), coderPostfix.Code(meta))); } internal static ICoder<T> WithPrefix<T>(this ICoder<T> coder, string prefix) where { var coderPrefix = new UnitCoder<T>(prefix); return new SequenceCoder<T>(new ICoder[] { coderPrefix, coder }, (meta, arr) => string.Join("", coderPrefix.Code(meta), coder.Code(meta))); }然后是statementCoder:
var statementCoder = basicCoder.WithPostfix(";");還可以被大括號(hào)包裹:
public static ICoder<T> Brace<T>(this ICoder<T> coder) { return coder.WithPostfix("}").WithPrefix("{"); }var braceStatementCoder = statementCoder.Brace();
可以實(shí)現(xiàn)重復(fù),也就是將一個(gè)ICoder<T>轉(zhuǎn)為一個(gè)ICoder<IEnumerable<T>>:
internal class RepeatedCoder<T> : CoderBase<IEnumerable<T>> { private readonly ICoder coder; private readonly string seperator; private readonly Func<T, bool> predicate; public RepeatedCoder(ICoder<T> coder, string seperator, Func<T, bool> predicate) { this.coder = coder; this.seperator = seperator; this.predicate = predicate; } public override string Code(IEnumerable<T> meta) { bool first = true; return meta.Where(m=>predicate(m)).Select(m => coder.Code(m)).Aggregate("", (val, cur) => { if (first) { first = false; return val + cur; } return val + seperator + cur; }); } }為了自己寫(xiě)代碼方便,直接把seperator和predicate邏輯硬塞進(jìn)去了,各位看官見(jiàn)諒。構(gòu)造一個(gè)重復(fù)Coder:
public static ICoder<IEnumerable<T>> Many<T>(this ICoder<T> coder, string seperator) where T : class { return Generator.GenRepeated(coder, seperator); }
var repeatedCoder = basicCoder.WithPostfix(";").Many("/n");這樣,給repeatedCoder一個(gè)Meta1的數(shù)組,他就會(huì)像一只coder一樣自動(dòng)把每個(gè)元素轉(zhuǎn)成一行代碼。有了這些還不夠,我們還是回歸需求本身。假設(shè)有這樣一個(gè)Coder :: ICoder<A>,這個(gè)Coder需要根據(jù)A的某個(gè)字段比如name寫(xiě)出來(lái)一個(gè) class name,需要根據(jù)另外一個(gè)比如IEnumerable<B>類型的字段寫(xiě)出一系列field的定義。我們期望生成的代碼形式:
class XXX{ public t1 aaa = v1; public t2 bbb = v2;}假設(shè)A的結(jié)構(gòu)定義是這樣的:
class A{ public string Name; public IEnumerable<Meta1> Fields;}其實(shí)這種需求也是我做出之前那種壞味代碼的原因,還是那句話,求高人指點(diǎn)!繼續(xù)上代碼,CombineCoder:
public static ICoder<T> GenCombine<T, T1>(ICoder<T> tCoder, ICoder<T1> t1Coder, Func<T, T1> selector) { return new SequenceCoder<T>(new ICoder[] { tCoder, t1Coder }, (meta, arr) => string.Format("{0}{1}", tCoder.Code(meta), t1Coder.Code(selector(meta)))); }復(fù)用我們之前構(gòu)造的repeatedCoder
var coder1 = Generator.GenBasic((A a) => string.Format("class {0}", a.Name)).WithPostfix("/n");var coder2 = repeatedCoder.Brace();現(xiàn)在我們希望一個(gè)A->string的coder1與一個(gè)IEnumerable<Meta1>->string的coder2 combine起來(lái),組合成一個(gè)A->string的classCoder,這樣做:
var classCoder = Generator.GenCombine(coder1, coder2, a => a.Fields);
好了大功告成,給classCoder一個(gè)A類型的元數(shù)據(jù)實(shí)例,就能輸出我們期望的字符串。
這篇博文的主體內(nèi)容其實(shí)也差不多告一段落了。誠(chéng)然,以上貼出的代碼不論是性能還是擴(kuò)展性都存在很大的問(wèn)題,但是前者對(duì)于一個(gè)codegen程序來(lái)說(shuō)并不是關(guān)鍵考慮因素;而后者,正如之前所說(shuō),代碼的壞味還是存在不
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注