Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could we try to start this project in F#? #4

Closed
ForNeVeR opened this issue Oct 8, 2018 · 17 comments
Closed

Could we try to start this project in F#? #4

ForNeVeR opened this issue Oct 8, 2018 · 17 comments
Labels
question Further information is requested

Comments

@ForNeVeR
Copy link
Collaborator

ForNeVeR commented Oct 8, 2018

The time has come.

I suggest we could try to write this project in F#. Here're my arguments.

Pros

  1. Obviously, @charlesroddie loves F# very much, we all know that :)
  2. I believe that working with immutable structures in F# is much more convenient than in C#, even with today's state of C# 7.3 (or even considering C# 8, F# is still better)
  3. In WPF-Math, we already had big troubles with mutability, and I had to invest a big part of my own time to fix all the issues and finally migrate to immutable structures, and I'm still unhappy with the result

Cons

  1. FSharp.Core is known to cause all kinds of troubles, e.g. TaskBuilder update breaks my tasks rspeele/TaskBuilder.fs#15, and we cannot develop in F# without FSharp.Core, obviously
  2. I maybe will repeat my old arguments from some old WPF-Math thread, but F# creates a barrier for C# guys to contribute, I have no doubts about that
  3. F# API may be hard for C# guys to consume (but we can create C#-friendly API without too much troubles)
  4. F# DU-based API is hard to extend externally

Open questions

  1. When porting WPF-Math atoms to immutable structures (a very relevant experience), I found a couple of places where I think F# records would not help me: in some places, I want to update a part of a structure (e.g. replace SourceSpan or Style). Each of my structures has SourceSpan property, so I had to essentially implement the same stupid method WithSourceSpan(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)
  2. If we're going to model our stuff using convenient F# ways: DU and records — then what are we going to do with external extendability? In OOP, external users may inherit from our Atoms and it would (hopefully) work well. But in F#, we have these options:
    • use object-oriented types in F# (we could still have small F# benefits here and there, but it won't be near as convenient as "native" F# way) and thus allow external users (WPF-Math and CSharpMath or anyone else) to extend our types as they want
    • use F#-native types (DU and records) and offer some functional way of extending things (I have no particular idea about what could we do)
    • forbid external extensions and require users to fit in our type system. From maintainer point of view, I found that thought very dangerous
    • some other options I don't see?

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 CI

Closing

@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 :)

@ForNeVeR ForNeVeR added the question Further information is requested label Oct 8, 2018
@gsomix
Copy link

gsomix commented Oct 8, 2018

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.

  • we can combine F#-native types and OOP. Let core library is safe F# code with records, DU and exhaustive checks. As DUs can implements interfaces we can introduce extensions points (e.g. IAtom) with a little amount of dynamic dispatch at library corners
  • OK, F# is not so good for updating nested immutable structures. One of possible solutions is to introduce composable lenses or so. Not sure how to make it usable from C# side

@charlesroddie
Copy link
Owner

charlesroddie commented Oct 8, 2018

Regardless of language, we should insist on:

  1. A linear structure with no cyclic dependencies.
  2. An Atom which either is a DU or mimics one.
  3. Immutability as default and as few hidden nulls as possible.

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.

C#-friendly F#

  • Using DUs from C# is no problem. The syntax for matching is identical compared to the CSharp DU-like class we would use, and creating cases is just NewRow(...) instead of new Row(...).
  • Option types can't be used easily from C#. When C# 8.0 comes out there will be better communication of nullability, but for now we could create constructors with optional arguments for C# consumption.

Cons of F#

  • Another dependency.
  • Not everyone can use F# (although most contributors here can I think).

Pros of F#

  • Cleaner for working with DUs and a large DU is at the heart of this project.
  • It enforces the rules at the top (linear, DUs, immutable defaults).
  • Much shorter code than C#, although I believe a C# solution that we would generate here would still be much easier to understand and work with than what exists at the moment.
  • Faster development, easier to write error-free code.

Non-cons of F#

  • FSharpCore versioning is OK now. Just create a new .Net SDK project, clean and just works.
  • We'd be using very simple parts of F#. Won't cause problems on any framework.
  • Mutability support is similar to C#, just not default. E.g. record fields can be mutable. Actually I would be very surprised if, after the cleanup that this project represents, we still need mechanisms to clone or mutate atoms.
  • Extensibility: as mentioned above, a clean C# structure has the same implications as using F#.

Regardless of language part 2

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 😊 .

@Happypig375
Copy link
Collaborator

Happypig375 commented Oct 9, 2018

@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.

@verybadcat
Copy link

verybadcat commented Oct 9, 2018

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.

@Happypig375
Copy link
Collaborator

@ForNeVeR
Copy link
Collaborator Author

ForNeVeR commented Oct 9, 2018

@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
);

@Happypig375
Copy link
Collaborator

Summarizing why not to use F#...

  • Not enough users. Should we dedicate an entire section of the README of the new project to stress why to learn F#?
  • The FSharp.Core dependency. We have to forego support of .NET Framework <4.5 and .NET Standard <2.0 (Not 1.6, not with the insane number of packages.). Currently wpf-math supports .NET Framework 4.0+ and CSharpMath supports .NET Standard 1.1+. This might be an acceptable change though.

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#.

@charlesroddie
Copy link
Owner

charlesroddie commented Oct 10, 2018

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.

@Happypig375
Copy link
Collaborator

Span and related APIs will be added in .NET Standard 2.1. Maybe we will upgrade to it once it is released.

@ForNeVeR
Copy link
Collaborator Author

@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:

https://github.com/ForNeVeR/wpf-math/blob/4da8b0a91db1e5cad04a4126c937e7ce37bffc9b/src/WpfMath/Atoms/IRow.cs#L6-L10

And here's one of the implementations:

https://github.com/ForNeVeR/wpf-math/blob/b05b6acee7d0d6fa476469b4b2a41a2c5bb8d4f1/src/WpfMath/Atoms/DummyAtom.cs#L21-L29

@ForNeVeR
Copy link
Collaborator Author

ForNeVeR commented Oct 11, 2018

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 Spans.

TL;DR:

  1. I think that we should target .NET Standard 2.0 in our library (but we could lower the version if we don't need any new stuff, that's a right idea).
  2. .NET Standard 1.x support should not be our concern.
  3. 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.

@ForNeVeR
Copy link
Collaborator Author

And, yeah, in conclusion about the topic: @Happypig375 do you think we should write MathAtom in C#?

@Happypig375
Copy link
Collaborator

@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#.

@ForNeVeR
Copy link
Collaborator Author

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.

@gsomix
Copy link

gsomix commented Oct 14, 2018

So, let's start coding? :) Please, create issues, I would be glad to work on some in my spare time.

@Happypig375
Copy link
Collaborator

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.

@charlesroddie
Copy link
Owner

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants