Closed ForNeVeR closed 5 years ago
Although I love F# too, I believe immutable-as-possible C# is right choice for the project. I am deeply convinced that libraries with base functionality should be accessible by as many contributors as possible. It also would be good to avoid additional dependencies like FSharp.Core
.
But let's talk about possible F# design first.
Extensibility: The Atom tree structure should be completely enumerated, regardless of language. The function which renders it needs to enumerate the possibilities to do its job. This makes things much cleaner, but requires an explicit mechanism for extensibility if that is needed. Perhaps a user defined parser element, Atom DU case, and rendering function step, activated by additing to a dictionary.
NewRow(...)
instead of new Row(...)
.This project starts with the Atom type. We can continue to model it in F# which should be easy to understand even to people who don't know F#. Converting the Atom to and from C# is easy. It's just a stretch with scale factor 4 and 1/4 respectively in the y direction 😊 .
@ForNeVeR Re: SourceSpan Can you elaborate more on WithSourceSpan? I can't find it in the wpf-math codebase, can't study your scenario. By the way, I plan to use System.ReadOnlyMemory\<char> in the atoms where a string might otherwise be. As experimented in Typography, System.Span and System.Memory's source can be backported to .NET Framework 4.0 without modifications, and 2.0 with slight modifications.
Re: Extensibility The atoms should really be a closed DU. If users want to extend it, they should inherit one of the cases. Otherwise, we won't be able to enjoy exhaustive switches.
@charlesroddie Re: DUs It's not so bad, especially compared to the current code.
//Declaration
[DiscriminatedUnionAttribute]
public class SomeDU
{
public class Case1 : SomeDU
{
public int Field { get; }
public Case1(int field) => Field = field;
}
public class Case2 : SomeDU
{
public float Field1 { get; }
public object Field2 { get; }
public Case1(float field1) => (Field1, Field2) = (field1, field2);
}
}
//Usage
SomeDU du = ...;
switch(du)
{
case SomeDU.Case1 case1:
DoStuff(case1);
break;
case SomeDU.Case2 case2:
DoStuff(case2);
break;
}
It's just a stretch with scale factor 4 and 1/4 respectively in the y direction 😊 .
You could trade width for height, depends on your preference.
For the other stuff, I agree with for now, especially with @gsomix.
I haven't worked enough with F# to be particularly helpful. What I will say about C# is that I find it annoying that one can't initialize immutable objects this way:
var foo = new ImmutableObject {
Field1 = "Bar",
Field2 = 10,
}
That setting inside the curly braces is considered "mutation", rather than "part of the constructor." But I think that's an annoyance, not a major factor in a language choice.
@verybadcat Opposing arguments here
@verybadcat quick notes on your case: I agree that it's annoying. In F# this syntax will work:
let foo: ImmutableObject = { // you may actually omit the `ImmutableObject` here
Field1 = "Bar"
Field2 = 10
}
In C#, a similar construct is probably not so bad:
var foo = ImmutableObject.Create(
field1: "Bar",
field2: 10
);
Summarizing why not to use F#...
Well, that's a short list. I'm now motivated to actually learn F# (I was only able to read but not write F# before). Green flag for F#.
I don't think .Net Standard support is related. The latest F# requires .Net Standard 1.6 and that is very conservative. I would just go with .Net Standard 2.0 for our library, which is highly compatible, is the standard standard version, has all the features (except new .net core stuff like span), and will remain usable for a long time.
Span and related APIs will be added in .NET Standard 2.1. Maybe we will upgrade to it once it is released.
@Happypig375
Can you elaborate more on WithSourceSpan? I can't find it in the wpf-math codebase, can't study your scenario.
Ah, yeah, it seems like I've finally eliminated need in that particular method. But I have WithPreviousAtom
, for example.
Here's where it's defined:
And here's one of the implementations:
Regarding .NET Standard stuff: I don't think that 1.x support is very relevant. It looks like .NET Standard 2.0 is better in all regards, and (almost?) every use case that supported 1.x supports 2.0 now.
AFAIK, Span<T>
lives in a library supported by .NET Standard 2.0: https://www.nuget.org/packages/System.Memory/
That means we could create our own Span
-based APIs in .NET Standard library.
Although, the .NET classes from the standard library doesn't support too much of Span
-based stuff in .NET Standard 2.0, that's true. E.g. in .NET Standard 2.0 you cannot do RandomNumberGenerator::GetBytes(Span)
, and I had to learn it in a hard way.
I don't think that Span
stuff is important for our task at hand: MathAtom is essentially just a thin library around the main Atom
type/DU hierarchy, right? We're not intending (yet) to add any parsing tasks that potentially could benefit from Span
s.
TL;DR:
Span
support in .NET Standard 2.0 should not be our concern.Later we could discuss if we want to migrate to .NET Standard 2.1 or maybe introduce conditional compilation to create blazing fast algorithms for .NET Core while keeping compatibility with .NET Framework's old ways.
And, yeah, in conclusion about the topic: @Happypig375 do you think we should write MathAtom in C#?
@ForNeVeR Sorry for forgetting this issue. I started supporting C#, but now I see there is really few reasons not to use F#. I choose F#.
Alright, I think we have a consensus here. I'll try to keep a close eye on C# compatibility and add some useful (automated) compat checks down the road.
So, let's start coding? :) Please, create issues, I would be glad to work on some in my spare time.
I will need to finish off verybadcat/CSharpMath#11 before CSharpMath can change its atom structure dramatically, as porting MathEditor relies on the original atom structure.
That said, @gsomix you can start on this independently for now.
@Happypig375 I will need to finish off verybadcat/CSharpMath#11 before CSharpMath can change its atom structure dramatically, as porting MathEditor relies on the original atom structure.
Good plan. If there are implications for the Atom type please post here.
The time has come.
I suggest we could try to write this project in F#. Here're my arguments.
Pros
Cons
Open questions
SourceSpan
orStyle
). Each of my structures hasSourceSpan
property, so I had to essentially implement the same stupid methodWithSourceSpan(SourceSpan ss) => this.Clone(ss: ss)
many times. We should think well about composability issues here (how should we compose the atoms, how could we implement typical workflow of CSharpMath or WPF-Math atom builder here)Atom
s and it would (hopefully) work well. But in F#, we have these options:General thoughts
It we're going to follow the F# route, we'll need to establish an uncommonly (for F#-based projects, I mean) C#-friendly environment:
documentation for C# users (with live samples about how to use our DU/record types from C# code)
create sample projects in C# (and make sure to compile them on our CI so they won't become outdated)
maybe add some integration tests to compile C# samples with our F# code and various versions of .NET, .NET Core, Mono and, mainly, FSharp.Core to avoid any crazy issues*
Remember, end-user may end up with multiple FSharp.Core versions in their end-user software, and we should be ready for that; hopefully, modern .NET Core SDK /
<PackageReference>
tooling will save us from major headaches, but still: I insist that we need to check that regularly and systematically as part of our CIClosing
@verybadcat, @alexreg I would appreciate your input very much, because I think you're much less biased towards F# than e.g. myself, @Happypig375, @charlesroddie or @gsomix.
Others: please also participate in the discussion, I appreciate your input as well :)