Skip to content

Commit 559693f

Browse files
Address comments.
1 parent 634eaaa commit 559693f

File tree

1 file changed

+45
-45
lines changed

1 file changed

+45
-45
lines changed

content/posts/monad-expressions-in-csharp.md

+45-45
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,49 @@ authors = ["Robert Marciniak"]
1414
# Why we can't have nice things, c# query expression edition
1515

1616
The closest thing to f#'s computational expression is either combination of `async` `await` keywords in contexts that
17-
do not always translate to asynchronous context or query expression, which does not always translate to querying something.
17+
do not always translate to asynchronous context or query expression which does not always translate to querying something.
1818

1919
That's because C# is a corporate enterprise language which tries to smuggle Nice Things through, but in a different,
2020
more corporate-enterprise-like form - not too abstract, sprinkled with "query SQL from code!"-, "Improve responsiveness without big refactors!"-, or similar seasoning.
2121

22+
## About query expression
23+
24+
[Query expression](https://docs.microsoft.com/en-us/dotnet/csharp/linq/query-expression-basics) is a syntactic sugar which allows compiler to translate
25+
26+
this
27+
```cs
28+
private Gen<TimeSeriesValueColumn> TimeSeriesValueColumnGen() =>
29+
from dto in TimeSeriesDtoGen()
30+
from time in Arb.Generate<DateTime>()
31+
select TimeSeriesValueColumn.CreateColumns(dto).Single();
32+
```
33+
34+
into this
35+
36+
```cs
37+
private Gen<TimeSeriesValueColumn> TimeSeriesValueColumnGen() =>
38+
TimeSeriesDtoGen()
39+
.SelectMany(dto => Arb.Generate<DateTime>(),
40+
(dto, time) => TimeSeriesValueColumn.CreateColumns(dto).Single());
41+
```
42+
43+
and can be added to any monad-like type simply by adding few functions
44+
with particular names and signatures, e.g. this short snippet allowed me to create valid code for 'case 2'
45+
46+
```cs
47+
public static class TaskExtensions
48+
{
49+
public static async Task<B> Bind<A, B>(this Task<A> @this, Func<A, Task<B>> fn)
50+
=> await fn(await @this);
51+
52+
public static async Task<B> Map<A, B>(this Task<A> @this, Func<A, B> fn)
53+
=> await Task.FromResult(fn(await @this));
54+
55+
public static Task<C> SelectMany<A, B, C>(this Task<A> @this, Func<A, Task<B>> fn, Func<A, B, C> select)
56+
=> @this.Bind(a => fn(a).Map(b => select(a, b)));
57+
}
58+
```
59+
2260
## 'what's the underlying type?'
2361

2462
### Case 1
@@ -31,7 +69,7 @@ select pair.date.AddDays(pair.index);
3169
```
3270

3371
```fs
34-
monad {
72+
seq {
3573
let! index = Enumerable.Range(1, 5)
3674
let! date = Enumerable.Repeat(DateTime.Today, 5)
3775
let pair = (index, date)
@@ -82,50 +120,12 @@ monad {
82120
yield pair |> (fun (i, (d:DateTime)) -> d.AddDays(i |> float))
83121
}
84122
```
85-
## ...converging into the same topic, never reaching it
123+
`monad` is from [F#+](http://fsprojects.github.io/FSharpPlus/), though implementing `option` Computation Expression manually is pretty straight-forward
86124

87-
These c# constructs do exactly the same thing, without any major or unsafe hacks. The main barrier from using these in a monad-like manner is their naming. Despite being perfectly viable in multiple scenarios their naming suggests using them in LINQ-to-SQL and asynchronous code respectively.
125+
## ...converging into the same topic, never reaching it
88126

127+
These c# constructs do exactly the same thing, without any major or unsafe hacks. The main barrier from using these in a monad-like manner is their naming. Despite being perfectly viable in multiple scenarios their naming suggests using them in LINQ-to-* and asynchronous code respectively.
89128

90129
- in c# there's no way to create a generic `Monad<T<E>>` because there's no higher-kinded polymorphism
91-
- we have poor type inference, no partial inference, can't have
92-
- query expression has no ability jump from one monad type to another using concise language syntax, while functional languages have transformers and one can nest computational expressions.
93-
94-
95-
## About query expression
96-
97-
Query expression is a syntactic sugar which allows compiler to translate
98-
99-
this
100-
```cs
101-
private Gen<TimeSeriesValueColumn> TimeSeriesValueColumnGen() =>
102-
from dto in TimeSeriesDtoGen()
103-
from time in Arb.Generate<DateTime>()
104-
select TimeSeriesValueColumn.CreateColumns(dto).Single();
105-
```
106-
107-
into this
108-
109-
```cs
110-
private Gen<TimeSeriesValueColumn> TimeSeriesValueColumnGen() =>
111-
TimeSeriesDtoGen()
112-
.SelectMany(dto => Arb.Generate<DateTime>(),
113-
(dto, time) => TimeSeriesValueColumn.CreateColumns(dto).Single());
114-
```
115-
116-
and can be added to any monad-like type simply by adding few functions
117-
with particular names and signatures, e.g. this short snippet allowed me to create case 2
118-
119-
```cs
120-
public static class TaskExtensions
121-
{
122-
public static async Task<B> Bind<A, B>(this Task<A> @this, Func<A, Task<B>> fn)
123-
=> await fn(await @this);
124-
125-
public static async Task<B> Map<A, B>(this Task<A> @this, Func<A, B> fn)
126-
=> await Task.FromResult(fn(await @this));
127-
128-
public static Task<C> SelectMany<A, B, C>(this Task<A> @this, Func<A, Task<B>> fn, Func<A, B, C> select)
129-
=> @this.Bind(a => fn(a).Map(b => select(a, b)));
130-
}
131-
```
130+
- poor type inference, no partial inference, partly due to complicated inheritance laws
131+
- query expression has no ability to switch from one monad type to another using concise language syntax, while functional languages have transformers and one can nest computational expressions.

0 commit comments

Comments
 (0)