iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0

接續昨天,我想要對這段程式碼做改善

var result = await GetMemberByNameAsync(name)
						.TaskBind(member => member
							.Map(m => new Attendance{ name = m.Name })
							.Map(CreateAttendanceAsync));

TaskExtension

在這邊先幫Task擴寫Bind,因為我之後想要用查詢式,所以順手就把Bind改成SelectMany了

public static class TaskExtension
{
    public static async Task<RR> SelectMany<T, R, RR>
        (this Task<T> source, Func<T, Task<R>> taskSelector, Func<T, R, RR> resultSelector)
    {
        T t = await source;
        R r = await taskSelector(t);
        return resultSelector(t, r);
    }
}

像在IEnumerable的SelectMany中用了兩次的foreach來把物件拿出盒子,這裡我也是用了兩次的await來達到類似的效果。

NullExtension

在最早之前講到了Option,在這邊我決定把相同的概念搬到Nullable的物件來

public static class NullExtension
{
    public static R? Select<T, R>(this T? source, Func<T, R> f)
        where T : class
        where R : class
        => source is { }
            ? f(source)
            : null;
    
    public static R? SelectMany<T, R>(this T? source, Func<T, R?> f)
        where T : class
        where R : class
        => source is { }
            ? f(source)
            : null;
    
    public static RR? SelectMany<T, R, RR>
        (this T? source, Func<T, R?> nullSelector, Func<T, R, RR> resultSelector)
        where T : class
        where R : class
        where RR : class
        => source is null
            ? null
            : nullSelector(source) is { } r
                ? resultSelector(source, r)
                : null;
}

這邊只針對參考型別做擴充,如果要對實值型別做擴寫的話請使用Nullable<T>

重構

接下來就是魔法的時刻,我可以把原來的code變成這樣

var result = 
		from t1 in GetMemberByNameAsync(name)
		// 為了把後面套用CreateAttendanceAsync拆出去,就需要把null判斷用Task包起來
    from a1 in Task.FromResult(t1.Select(Attendance.Create))
    from a2 in a1.Select(CreateAttendanceAsync)
    select a2;

public class Attendance
{
    public string Name { get; set; }
		// 在這裡直接用工廠方法方便調用
    public static Attendance Create(string name) => new Attendance { Name = name };
}

t1是把GetMemberByNameAsync的結果取出,所以他的型別是一個string?,我在第二行將Map(使用select)映射成Attendance?,並且放到Task monad中,也就是說a1的型別是Attendance?,以此類推,最後result的型別會是Task<string>?。這邊因為有了SelectMany的多載,使得我能夠將CreateAttendanceAsync拆出去,而SelectMany接受的方法是由T → C<T>,也就是說我必須將string?→Attendance?再用一層Task容器包住。改用查詢運算式來寫的話整個邏輯是不是清楚非常多呢?我當初看到code的時候訝異了非常久,第一個念頭是這樣到底有沒有辦法編譯過,結果經過一番加工後真的可行!

小結

C#的查詢運算式其實保留了非常大的彈性,為任意的monad擴充專屬的Linq查詢方法,就可以套用到查詢運算式的形式,另外我們可以看到SelecMany真的非常重要,回想一下前面介紹過Monad laws中的Left identity,就是今天範例中查詢運算式裡面第二行阿!


上一篇
Day20. SelectMany(1)
下一篇
Day22. Query-Expression Pattern
系列文
Functional Programming with C#30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言