I can simplify it for you:
public class TestClass : Record<TestClass>
{
public readonly int X;
public readonly string Y;
public readonly Guid Z;
public TestClass(int x, string y, Guid z)
{
X = x;
Y = y;
Z = z;
}
}
That is an immutable type that has structural equality and ordering, a strong GetHashCode() implementation, as well as a default ToString() which previews the members, and serialization and deserialization constructs. It's a feature I recently added to language-ext [1].If you need `With` functionality, then add the following member:
public TestClass With(int? X = null, string Y = null, Guid? Z = null) =>
new TestClass(X ?? this.X, Y ?? this.Y, Z ?? this.Z);
Then you can use named parameters to do partial updates: value = value.With(X: 456, Y: "World");
Obviously it's still more boilerplate than F#, but C# is never going to have significantly less until we get proper record types and ADTs.> Functional programming is also seeping in but again tons of boiler plate.
Do you have an example? It's something I've worked on quite heavily over the past few years, so I'm always looking for areas to try and improve the lot of C# devs trying to write functionally.
[1] https://github.com/louthy/language-ext/releases/tag/v2.1.0
Without it it's useless - and do that example when fields aren't named X, Y, Z but have actual descriptive property names + 5-10 properties like DB tables.
It gets ridiculous really fast.
>Do you have an example?
Record types, result piping (best you can do is extension methods - again boilerplate), pattern matching, etc.
I recently wrote a C# report integration service - it was a C# service that connected to a DB at specific time intervals, did a bunch of data transformations then passed it off to different services over file system or TCP. Wrote it functional style with C# (immutable collections, record types, static functions).
I then rewrote the same project in F# because I wanted how much simpler the code would be in practice - off the top of my head I managed to reduce a ~20k LOC project filled with boilerplate to ~6k LOC of pure business logic data transformation and protocol specific data formatting. IIRC I had 5 logically grouped source files in F# that had connected logic inside of them (ie. record types were "one liners" defined in the same file they were used not a separate file like the C# tediousness)
The resulting code was easier to read because there was just less crap, easier to test from REPL/debug isolated issues and unit test.
C# is not Java but it's still ridiculously verbose for high level programming.
public class TestClass
{
public int X { get; }
public string Y { get; }
public Guid Z { get; }
public TestClass(int x, string y, Guid z)
{
X = x;
Y = y;
Z = z;
}
}
And C# 7.0 shortens this to: public class TestClass(
int X,
string Y,
Guid Z);Why would I want to? It's more boilerplate for no gain. I have never understood the obsession with properties over fields. I realise that to some serialisation or interop libraries that rely on reflection to access properties it's important, but to my mind, those solutions should also support public fields. And obviously inheritance-land with interfaces, but I find writing in a functional style that interfaces tend to be a rarity rather than the default setting.
> though I have no idea if this works with your "Record" class.
Yes, field backed properties are supported in the structural equality and ordering.
> And C# 7.0 shortens this to...
It doesn't. That maybe coming in a later release, but neither C# 7.0 nor 7.1 support the record syntax.
public class TestClass
{
public int X { get; }
public string Y { get; }
public Guid Z { get; }
public TestClass(int x, string y, Guid z) => (X, Y, Z) = (x, y, z);
}
Disclaimer: I don't combine expression-bodied constructors and tuple construction/deconstruction in my code. I will use either feature but the combination seems likely to annoy my coworkers. I also haven't measured the costs of this idiom.[0] https://www.manning.com/books/c-sharp-in-depth-fourth-editio...
Encapsulation, allowing to change the internal representation without requiring clients to update or recompile their code.
Ensuring the class invariants are always kept valid.
This benefit is exaggerated. In practice, classes end up being either record-like where you need visibility on all the actual fields, or they are protocol-like/more fully encapsulated with few public members and more methods. The addition of properties was a big mistake IMO.
Read/write fields should just be part of interface specifications. There is little value beyond that.
Properties go all the way back to Smalltalk, which doesn't expose class internal data.
> Read/write fields should just be part of interface specifications. There is little value beyond that.
No because you cannot ensure their invariants, specially if more than one field needs to change at the same time.
Right, because Smalltalk doesn't conflate the use of public and internal members. Properties are second class citizens on .Net, since you can't pass them as ref or out arguments, but we can't use fields which are first class citizens because they have inadequate permissions control.
Then properties exist, but also don't really exist at the reflection level, where you have to search for getters and setters by mangled name if they're private. It's a total frickin mess.
> No because you cannot ensure their invariants, specially if more than one field needs to change at the same time
Properties don't help with changing more than one field either, so I'm not what your point is.
And you can ensure invariants with field permissions, which is what I suggested. Publicly read-only but privately writable is all you need, then the values change via method invocations. Most use of properties just add permissions to field access anyway.
Read/write with default const/init-only fields should have been the default. Then you can't pass read-only fields as ref or out arguments due to permissions, not because they're second class citizens.
There is nothing at IL level that prevents it, they can eventually lift the restriction if they feel like it.
> Properties don't help with changing more than one field either, so I'm not what your point is.
The setter can do a cascade set of changes while keeping the invariants.
While theoretically true, it will never happen. The whole point of ref/out parameters is efficient, direct pointer access to a member. This can never be made efficient for properties. The CLR GC is carefully tuned to handle internal pointers like this, so quite a bit of engineering has gone into making this efficient and safe, which means efficiency is important.
Furthermore, properties are entirely redundant with the simple change I suggested. They literally serve no purpose once you have permissions on fields, since methods cover the remaining use cases.
> The setter can do a cascade set of changes while keeping the invariants.
And so can methods. If you have mutating this much state, you should be invoking a method anyway. Properties are not the place for sophisticated behaviour.
Rust does its best given the circumstances, but people's top complaint is that its hard to learn.
Scala never went Mainstream because you can't hire developers, and training them takes many months.
F# should not repeat that mistake.
https://github.com/rspeele/Rezoom.SQL
It can't check everything that the other providers can, but IMO you get most of the benefit without requiring any onerous build-time dependencies.
You can pass `LocalSchemaFile = "foo.ssdl", ForceUpdate = false` to the provider, and it will only try to connect to the database if the file foo.ssdl doesn't exist; if it does, then the provider just uses it to compute the schema. When the schema changes, you just need to set ForceUpdate to true, wait for the IDE to pull the changes from the server and write foo.ssdl, then set ForceUpdate back to false, and you can commit the changed file.
Here's the context that makes it not a big deal:
1) If there is some fundamental issue with having a dev DB then you would just use another ORM, just like in C#. Really, this specific type provider is not an ORM, it's for SQL server data munging. Most every project I've seen in a decade has multiple dev DBs, many used in testing, many used for staging... being able to talk to a valid schema source should be relatively straightforward... but if it isnt then get a different type provider or use an ORM with explicit code generation.
2) Compile time guarantees about data sources is a huge source of comfort when dealing with DBs owned by external actors or teams. For data scientist, integrator, sys admins, and such the norm is connecting to operational DBs that you have very little control over. This specific Type Provider ensures that your scripts will not compile if there has been a breaking schema change. That means you're seeing these issues during deployment, not as costly runtime errors that may or may not corrupt your application state. Less debugging, more safety, better guarantees, immediate feedback on issues; all in realtime.
To summarize: if compile time validation of JSON structures, XML structures, SQL Schema, or custom file formats creates some kind of pain, then don't use a provider of types that relies on analyzing data structures to create code :)
For the other 99.9999% of the time type providers are a DREAM for working with external data sources, provide most of what we want ORMs for, and are very lightweight in comparison. It's rapid data exploration with built in prototyping. Very nice.
The bar has moved so much on this one. I remember when C#'s wonderful lambda syntax, along with all the extension methods that LINQ added, qualified it as having pretty good functional programming support. Now that other languages have gone out and copied sane lambdas, and "Map" is in libraries left and right, C# has to keep reaching for that higher and higher bar.
* Immutable record types
* ADTs with completeness pattern matching
* Higher-kinds
F# is also missing higher kinds, but can do the others. Did you have something else in mind?
Immutable records also makes it easier to share data safely, thus making scaling across cores easier in some cases, without worrying about plan interference.
Immutable records are already possible today in C#, even if a bit more verbose than on F#, plus EF code is anyway mostly generated.
In pretty much all of them we ended up writing a simple wrapper around the database that was much easier to control.
I found that a good solution is to write a generic sql serialiser that does all the basic operations (insert, update, select, delete, read one or multiple from readers) and do the rest by hand. It avoids most of the tedious code/sql while not getting in your way to do things like transactions, bulk inserts, etc
Contrast that with type providers - they expose SQL to the compiler - this way you get compile time SQL query checking, type checking (it generates types for SQL queries) and it's manipulating data as input and output with known standard SQL semantics.
Usually the result is performance problems.
Immutable data is also becoming a common pattern even in C# but it's tedious as hell to write.
Functional programming is also seeping in but again tons of boiler plate.
Biggest problem with F# is that it has it's own warts + it's a big upfront switching cost and a steep learning curve but it offers many marginal improvements - when you sum those improvements it ends up being worth it IMO but that's a very hard sell.