{"id":3096,"date":"2013-04-03T10:53:21","date_gmt":"2013-04-03T10:53:21","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/pfxteam\/2013\/04\/03\/tasks-monads-and-linq\/"},"modified":"2013-04-03T10:53:21","modified_gmt":"2013-04-03T10:53:21","slug":"tasks-monads-and-linq","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/tasks-monads-and-linq\/","title":{"rendered":"Tasks, Monads, and LINQ"},"content":{"rendered":"<p>A few years back, Wes Dyer wrote a <a href=\"https:\/\/blogs.msdn.com\/b\/wesdyer\/archive\/2008\/01\/11\/the-marvels-of-monads.aspx\">great post on monads<\/a>, and more recently, Eric Lippert wrote a terrific blog series exploring <a href=\"http:\/\/ericlippert.com\/category\/monads\/\">monads and C#<\/a>. In that series, Eric alluded to Task&lt;TResult&gt; several times, so I thought I&rsquo;d share a few related thoughts on Task&lt;TResult&gt; and the async\/await keywords.<\/p>\n<p>As both Wes and Eric highlight, a monad is a triple consisting of a type, a Unit function (often called Return), and a Bind function. If the type in question is Task&lt;T&gt;, what are its Unit and Bind functions? <\/p>\n<p>The Unit operator takes a T and &ldquo;amplifies&rdquo; it into an instance of the type:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public static<\/font> <font color=\"#4bacc6\">M<\/font>&lt;T&gt; Unit&lt;T&gt;(<font color=\"#0000ff\">this<\/font> T value);<\/font><\/p>\n<\/blockquote>\n<p>That&rsquo;s exactly what Task.FromResult does, producing a Task&lt;T&gt; from a T, so our Unit method can easily be implemented as a call to FromResult:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public static<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; Unit&lt;T&gt;(<font color=\"#0000ff\">this<\/font> T value)         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> <font color=\"#4bacc6\">Task<\/font>.FromResult(value);         <br>}<\/font><\/p>\n<\/blockquote>\n<p>What about Bind? The Bind operator takes the instance of our type, extracts the value from it, runs a function over that value to get a new amplified value, and returns that amplified value:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public static<\/font> <font color=\"#4bacc6\">M<\/font>&lt;V&gt; Bind&lt;U, V&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">M<\/font>&lt;U&gt; m, <font color=\"#4bacc6\">Func<\/font>&lt;U, <font color=\"#4bacc6\">M<\/font>&lt;V&gt;&gt; k);<\/font><\/p>\n<\/blockquote>\n<p>If you squint at this, and if you&rsquo;ve read my previous blog post <a href=\"https:\/\/blogs.msdn.com\/b\/pfxteam\/archive\/2012\/08\/15\/implementing-then-with-await.aspx\">Implementing Then with Await<\/a>, the structure of this declaration should look eerily like the last overload of Then discussed:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public static<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;TNewResult&gt; Then&lt;TResult, TNewResult&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;TResult&gt; task, <font color=\"#4bacc6\">Func<\/font>&lt;TResult, <font color=\"#4bacc6\">Task<\/font>&lt;TNewResult&gt;&gt; continuation);<\/font> <\/p>\n<\/blockquote>\n<p>In fact, other than the symbols chosen, they&rsquo;re identical, and we can implement Bind just as we implemented Then:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;V&gt; Bind&lt;U, V&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;U&gt; m, <font color=\"#4bacc6\">Func<\/font>&lt;U, <font color=\"#4bacc6\">Task<\/font>&lt;V&gt;&gt; k)         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return await<\/font> k(<font color=\"#0000ff\">await<\/font> m);         <br>}<\/font><\/p>\n<\/blockquote>\n<p>This is possible so concisely because await and async are so close in nature to the monadic operators. When you write an async function that returns Task&lt;V&gt;, the compiler expects the body of the method to return a V, and it lifts (or amplifies) that V into the Task&lt;V&gt; that&rsquo;s returned from the method call; this is basically Unit (async, of course, also handles the creation and completion of the returned Task for the case where an exception propagates out of the body of the async method). Further, a core piece of the Bind operator is in extracting the value from the supplied instance, and that&rsquo;s nicely handled by await.<\/p>\n<p>In Eric&rsquo;s <a href=\"http:\/\/ericlippert.com\/2013\/04\/02\/monads-part-twelve\/\">last post on monads<\/a>, he talks about some of the C# LINQ operators, and how they can easily be implemented on top of types that correctly implement a Unit and a Bind method:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">static<\/font> <font color=\"#4bacc6\">M<\/font>&lt;C&gt; SelectMany&lt;A, B, C&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">M<\/font>&lt;A&gt; monad,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;A, <font color=\"#4bacc6\">M<\/font>&lt;B&gt;&gt; function,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;A, B, C&gt; projection)         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> monad.Bind(         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outer =&gt; function(outer).Bind(         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inner =&gt; projection(outer, inner).Unit()));         <br>}<\/font><\/p>\n<\/blockquote>\n<p>Sure enough, with our Bind and Unit implementations around Task&lt;T&gt;, we can substitute in &ldquo;Task&rdquo; for &ldquo;M&rdquo;, and it &ldquo;just works&rdquo;:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#666666\"><font color=\"#a5a5a5\">static<\/font> <\/font><font color=\"#4bacc6\">Task<\/font>&lt;C&gt; <font color=\"#a5a5a5\">SelectMany&lt;A, B, C&gt;(          <br><\/font>&nbsp;&nbsp;&nbsp; <font color=\"#a5a5a5\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;A&gt; <font color=\"#a5a5a5\">monad,<\/font>         <br>&nbsp;&nbsp;&nbsp; <font color=\"#a5a5a5\">Func&lt;A, <font color=\"#000000\"><font color=\"#4bacc6\">Task<\/font>&lt;B&gt;<\/font>&gt; function,           <br>&nbsp;&nbsp;&nbsp; Func&lt;A, B, C&gt; projection)           <br><\/font><font color=\"#a5a5a5\">{          <br>&nbsp;&nbsp;&nbsp; return monad.Bind(           <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outer =&gt; function(outer).Bind(           <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inner =&gt; projection(outer, inner).Unit()));           <br>}<\/font><\/font><\/p>\n<\/blockquote>\n<p>What does it mean here to &ldquo;just work&rdquo;? It means we can start writing LINQ queries using the C# query comprehension syntax with operators that rely on SelectMany, e.g.<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">int<\/font> c = <font color=\"#0000ff\">await<\/font> (<font color=\"#0000ff\">from<\/font> first <font color=\"#0000ff\">in<\/font> <font color=\"#4bacc6\">Task<\/font>.Run(() =&gt; 1)         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">from<\/font> second <font color=\"#0000ff\">in<\/font> <font color=\"#4bacc6\">Task<\/font>.Run(() =&gt; 2)         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">select<\/font> first + second);         <br><font color=\"#4bacc6\">Console<\/font>.WriteLine(c == 3); <font color=\"#008040\">\/\/ will output True<\/font><\/font><\/p>\n<\/blockquote>\n<p>Of course, we can implement SelectMany without Bind and Unit, just using async\/await directly:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;C&gt; SelectMany&lt;A, B, C&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;A&gt; task,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;A, <font color=\"#4bacc6\">Task<\/font>&lt;B&gt;&gt; function,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;A, B, C&gt; projection)         <br>{         <br>&nbsp;&nbsp;&nbsp; A a = <font color=\"#0000ff\">await<\/font> task;         <br>&nbsp;&nbsp;&nbsp; B b = <font color=\"#0000ff\">await<\/font> function(a);         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> projection(a, b);         <br>}<\/font><\/p>\n<\/blockquote>\n<p>In fact, we can implement many of the LINQ query operators simply and efficiently using async\/await. The <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms228593.aspx\">C# specification<\/a> section 7.16.3 lists which operators we need to implement to support all of the C# query comprehension syntax, i.e. all of the LINQ contextual keywords in C#, such as select and where. Some of these operators, like OrderBy, make little sense when dealing with singular values as we have with Task&lt;T&gt;, but we can easily implement the others. This enables using most of the C# LINQ query comprehension syntax with Tasks:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;V&gt; SelectMany&lt;T, U, V&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; source, <font color=\"#4bacc6\">Func<\/font>&lt;T, <font color=\"#4bacc6\">Task<\/font>&lt;U&gt;&gt; selector, <font color=\"#4bacc6\">Func<\/font>&lt;T, U, V&gt; resultSelector)         <br>{         <br>&nbsp;&nbsp;&nbsp; T t = <font color=\"#0000ff\">await<\/font> source;         <br>&nbsp;&nbsp;&nbsp; U u = <font color=\"#0000ff\">await<\/font> selector(t);         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> resultSelector(t, u);         <br>}         <\/p>\n<p><font color=\"#00ff00\"><font color=\"#0000ff\">public static async<\/font> <\/font><font color=\"#4bacc6\">Task<\/font>&lt;U&gt; Select&lt;T, U&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; source, <font color=\"#4bacc6\">Func<\/font>&lt;T, U&gt; selector)         <br>{         <br>&nbsp;&nbsp;&nbsp; T t = <font color=\"#0000ff\">await<\/font> source;         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> selector(t);         <br>}         <\/p>\n<p><font color=\"#0000ff\">public static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; Where&lt;T&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; source, <font color=\"#4bacc6\">Func<\/font>&lt;T, <font color=\"#0000ff\">bool<\/font>&gt; predicate)         <br>{         <br>&nbsp;&nbsp;&nbsp; T t = <font color=\"#0000ff\">await<\/font> source;         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">if<\/font> (!predicate(t)) <font color=\"#0000ff\">throw new<\/font> <font color=\"#4bacc6\">OperationCanceledException<\/font>();         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> t;         <br>}         <\/p>\n<p><font color=\"#0000ff\">public static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;V&gt; Join&lt;T, U, K, V&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; source, <font color=\"#4bacc6\">Task<\/font>&lt;U&gt; inner,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;T, K&gt; outerKeySelector, <font color=\"#4bacc6\">Func<\/font>&lt;U, K&gt; innerKeySelector,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;T, U, V&gt; resultSelector)         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">await<\/font> <font color=\"#4bacc6\">Task<\/font>.WhenAll(source, inner);         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">if<\/font> (!<font color=\"#4bacc6\">EqualityComparer<\/font>&lt;K&gt;.Default.Equals(         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outerKeySelector(source.Result), innerKeySelector(inner.Result)))         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">throw new<\/font> <font color=\"#4bacc6\">OperationCanceledException<\/font>();         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> resultSelector(source.Result, inner.Result);         <br>}         <\/p>\n<p><font color=\"#0000ff\">public static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;V&gt; GroupJoin&lt;T, U, K, V&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; source, <font color=\"#4bacc6\">Task<\/font>&lt;U&gt; inner,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;T, K&gt; outerKeySelector, <font color=\"#4bacc6\">Func<\/font>&lt;U, K&gt; innerKeySelector,         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;T, <font color=\"#4bacc6\">Task<\/font>&lt;U&gt;, V&gt; resultSelector)         <br>{         <br>&nbsp;&nbsp;&nbsp; T t = <font color=\"#0000ff\">await<\/font> source;         <br>&nbsp;&nbsp;&nbsp; <\/font><font face=\"Consolas\"><font color=\"#0000ff\">return<\/font> resultSelector(t,         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inner.Where(u =&gt; <font color=\"#4bacc6\">EqualityComparer<\/font>&lt;K&gt;.Default.Equals(         <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outerKeySelector(t), innerKeySelector(u))));         <br><\/font><font face=\"Consolas\">}        <\/p>\n<p><font color=\"#0000ff\">public static async<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;T&gt; Cast&lt;T&gt;(<font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">Task<\/font> source)         <br>{         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">await<\/font> source;         <br>&nbsp;&nbsp;&nbsp; <font color=\"#0000ff\">return<\/font> (T)((<font color=\"#0000ff\">dynamic<\/font>)source).Result;         <br>}<\/font><\/p>\n<\/blockquote>\n<p>Interestingly, Task&lt;TResult&gt; already has the members necessary to be considered &ldquo;comonadic.&rdquo; As Brian Beckman discusses in <a href=\"https:\/\/github.com\/iSynaptic\/Monad-Comonad-Precis\/blob\/master\/Precis.cs\">his precis<\/a>, a comonad is the dual of a monad, a triple consisting of the type and two operators: Extract (the flip of Unit\/Return) and Extend (the flip of Bind). Here I&rsquo;ve taken a few liberties with the signatures from what Brian outlines, such as swapping the order of some of the parameters:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public<\/font> T Extract&lt;T&gt;(<font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">W<\/font>&lt;T&gt; self);         <br><font color=\"#0000ff\">public<\/font> <font color=\"#4bacc6\">W<\/font>&lt;U&gt; Extend&lt;T, U&gt;(<font color=\"#0000ff\">this<\/font> <font color=\"#4bacc6\">W<\/font>&lt;T&gt; self, <font color=\"#4bacc6\">Func<\/font>&lt;<font color=\"#4bacc6\">W<\/font>&lt;T&gt;, U&gt; func);<\/font><\/p>\n<\/blockquote>\n<p>Task&lt;TResult&gt; already supports Extract, it&rsquo;s just called Result:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public<\/font> TResult Result;<\/font><\/p>\n<\/blockquote>\n<p>And it already supports Extend, it&rsquo;s just called ContinueWith:<\/p>\n<blockquote>\n<p><font face=\"Consolas\"><font color=\"#0000ff\">public<\/font> <font color=\"#4bacc6\">Task<\/font>&lt;TNewResult&gt; ContinueWith&lt;TNewResult&gt;(         <br>&nbsp;&nbsp;&nbsp; <font color=\"#4bacc6\">Func<\/font>&lt;<font color=\"#4bacc6\">Task<\/font>&lt;TResult&gt;, TNewResult&gt; func);<\/font><\/p>\n<\/blockquote>\n<p>(In truth, to correctly implement all of the comonadic laws Brian outlines, we&rsquo;d likely want to tweak both of these with a thin layer of additional code to modify some corner cases around exceptions and cancellation, due to how Result propagates exceptions wrapped in AggregateException and how ContinueWith tries to match thrown OperationCanceledExceptions against the CancellationToken supplied to ContinueWith. But the basic idea stands.)<\/p>\n<p>Most of the posts I write on this blog are about practical things. So, is any of this really useful in everyday coding with Tasks and async\/await? Would you actually want to implement and use the LINQ surface area directly for Tasks? Eh, probably not. But it&rsquo;s a fun to see how all of these things relate.<\/p>\n<p><em>(Update 4\/3\/2013: Thanks to Aaron Lahman for pointing out the bug I had in my GroupJoin implementation.&nbsp; Fixed.)<\/em><\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A few years back, Wes Dyer wrote a great post on monads, and more recently, Eric Lippert wrote a terrific blog series exploring monads and C#. In that series, Eric alluded to Task&lt;TResult&gt; several times, so I thought I&rsquo;d share a few related thoughts on Task&lt;TResult&gt; and the async\/await keywords. As both Wes and Eric [&hellip;]<\/p>\n","protected":false},"author":360,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[7908],"tags":[7925,36,7909,7912],"class_list":["post-3096","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-pfxteam","tag-net-4-5","tag-async","tag-parallel-extensions","tag-task-parallel-library"],"acf":[],"blog_post_summary":"<p>A few years back, Wes Dyer wrote a great post on monads, and more recently, Eric Lippert wrote a terrific blog series exploring monads and C#. In that series, Eric alluded to Task&lt;TResult&gt; several times, so I thought I&rsquo;d share a few related thoughts on Task&lt;TResult&gt; and the async\/await keywords. As both Wes and Eric [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/3096","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/360"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=3096"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/3096\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=3096"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=3096"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=3096"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}