charlesroddie / MathAtom

Structural representations of visual mathematics expressions for use in .Net rendering libraries
MIT License
7 stars 0 forks source link

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

Closed ForNeVeR closed 5 years ago

ForNeVeR commented 6 years ago

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. https://github.com/rspeele/TaskBuilder.fs/issues/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:

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

gsomix commented 6 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.

charlesroddie commented 6 years ago

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

Cons of F

Pros of F

Non-cons of 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 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

@verybadcat Opposing arguments here

ForNeVeR commented 6 years ago

@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 commented 6 years ago

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

charlesroddie commented 6 years ago

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 commented 6 years ago

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

ForNeVeR commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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

Happypig375 commented 6 years ago

@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 commented 6 years ago

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 commented 6 years ago

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

Happypig375 commented 6 years ago

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 commented 6 years ago

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