Some examples related to my tweet rant https://twitter.com/dsymetweets/status/1294276915260522496
In project programming this hit me this week with a bug:
value.Format(...);
return true;
when I actually wanted
return value.Format(...);
This is "implicit information loss" and is almost as corrosive to accurate programming as null pointers. Similarly in a C# notebook there is a subtle difference between
System.AppDomain.GetCurrentThreadId()
and
System.AppDomain.GetCurrentThreadId();
The first evaluates an expression and displays it, the second evaluates the expression and discards it.
No language in the 21st Century should have implicit information loss. There are likely C# analyzers to prevent this kind of bug, or you have other choices.
2. "No object expressions" (was "no way to implement interfaces or abstract classes using an expression, meaning stupid extra classes")
Link: F# Object expressions
Let's say you want to implement IComparer<T>
(or some other interface where there's no existing way of
doing it).
You have to do this:
Array.Sort(candidates, new SortByRelevanceAndOrder<(Type type, string mimeType, int index)>(tup => tup.type, tup => tup.index));
private class SortByRelevanceAndOrder<T> : IComparer<T>
{
Func<T, Type> _typeKey;
Func<T, int> _indexKey;
public SortByRelevanceAndOrder(Func<T, Type> typeKey, Func<T, int> indexKey)
{
_typeKey = typeKey;
_indexKey = indexKey;
}
public int Compare(T inp1, T inp2)
{
var type1 = _typeKey(inp1);
var type2 = _typeKey(inp2);
var index1 = _indexKey(inp1);
var index2 = _indexKey(inp2);
if (type1.IsRelevantFormatterFor(type2) && type2.IsRelevantFormatterFor(type1))
return Comparer<int>.Default.Compare(index1, index2);
else if (type1.IsRelevantFormatterFor(type2))
return 1;
else
return -1;
}
}
The class is pointless and shouts out "C# can't create object instances in expressions!". Instead you should be able to implement an interface using an expression, e.g. like F# object expressions
let comparer =
{ new IComparer<_> with
member x.Compare((type1: Type, index1: string), (type2, index2)) =
if (type1.IsRelevantFormatterFor(type2) && type2.IsRelevantFormatterFor(type1))
return compare index1 index2
else if (type1.IsRelevantFormatterFor(type2))
return 1
else
return -1
}
Array.Sort(candidates, comparer)
There are hundreds of examples of this (many of them involving significant variable capture too) and the absence of this feature makes C# very problematic as a mixed functional-object language. You can be functional with delegates, or write classes the old way, but implementing functional objects in functional code is very contorted, verbose and error prone.
Note this is not "inner classes" of Java which is a complexifying, not simplifying beast.
You should not do functional-object programming in a language without this feature.
This is well known (they tried to get it in to C# 6) but basically why write all this
private class SortByRelevanceAndOrder<T> : IComparer<T>
{
Func<T, Type> _typeKey;
Func<T, int> _indexKey;
public SortByRelevanceAndOrder(Func<T, Type> typeKey, Func<T, int> indexKey)
{
_typeKey = typeKey;
_indexKey = indexKey;
}
public int Compare(T inp1, T inp2)
{
var type1 = _typeKey(inp1);
var type2 = _typeKey(inp2);
var index1 = _indexKey(inp1);
var index2 = _indexKey(inp2);
...
}
}
when you could write this (defining a constructor implicitly)
private class SortByRelevanceAndOrder<T>(Func<T, Type> typeKey, Func<T, int> indexKey) : IComparer<T>
{
public int Compare(T inp1, T inp2)
{
var type1 = typeKey(inp1);
var type2 = typeKey(inp2);
var index1 = indexKey(inp1);
var index2 = indexKey(inp2);
...
}
This means that when doing either object-programming or functional-object programming you have to continually manually populate the fields of a class and makes it harder to fluently move code from expressions to members.
This is a chronic problem for C# when writing any kind of object programming code. C# programmers are always field-converting by hand as a result.
This feature has been in many languages since the 90s (I'm not sure of the first - OCaml had it ~1994). In my opinion, any object-programming language should support this feature in the 21st Century.
Here's an example of a C# DSL for HTML generation. It's ok C# (the problem is the language not the code)
var headers = new List<IHtmlContent>();
headers.Add(th(i("index")));
headers.AddRange(df.Columns.Select(c => (IHtmlContent) th(c.Name)));
var rows = new List<List<IHtmlContent>>();
var take = 20;
for (var i = 0; i < df.Rows.Count; i++)
{
var cells = new List<IHtmlContent>();
cells.Add(td(i));
foreach (var obj in df.Rows[i])
{
cells.Add(td(obj));
}
rows.Add(cells);
}
var t = table(
thead(
headers),
tbody(
rows.Select(
r => tr(r))));
writer.Write(t);
Note that we've had to create mutable data for collections along the way. This is because C# doesn't have anything like F# list/sequence/array expressions beyond "C# iterator methods" which would be way overkill for this kind of generation. Compare with this:
table [] [
thead [] [
th [] [ str "Index" ]
for c in df.Columns do
th [] [ str c.Name]
]
tbody [] [
for i in 0 .. int df.Rows.Count do
tr [] [
td [] [ embed i ]
for o in df.Rows.[int64 i] do
td [] [ embed o ]
]
]
]
Note that
-
this is an expression
-
no mutation
-
squint and it looks like HTML (used widely in F# server-sdie and client side code)
-
irregular list generation is supported (you can emit a header, then do a loop)
-
conditional fragments of list generation supported
-
same syntax for computing arrays, lists, sequences
There is some syntax in C# using ParamArray (params
) arguments extensively plus also LINQ, or collection
initializers, or even LINQ queries. But
-
this tends not to cope with truly generative situations where elements are in the collection conditionally, or loops are used, or collections are appended together.
-
in practice this needs to use a really contorted mix of language features, and still won't look remotely like HTML.
The fundamental feature missing here is a lightweight way of building immutable list/collection expressions, including generatively.
Strong language features for succinct immutable data generation are essential for client and server programming today.
Well, it is.