Giter VIP home page Giter VIP logo

Comments (11)

louthy avatar louthy commented on May 21, 2024

For people not used to functional programming things like Fold, Reduce sound strange and it would be of great help to play with the methods and their overloads without the need to search online docs or decompile the code.

Yup I agree, I've been going through as much as I can documenting the code. There is actually a section in the README.md that goes through this, but it needs more structure. This library started out as a pet project that kinda got a lot more popular than I expected, so I was caught slightly off-guard by it - the ReadMe was/is more like a blog post and needs breaking out. It's now quite large, but as the API is settling I am putting more time into the documentation.

My first thought when reading about implementing actor model was why not use akka instead, and I later found the git issue discussing this.

It's at the end of the ReadMe ;)

But yeah, all valid points which I hope to work on more over the new few months or so.

from language-ext.

Jagged avatar Jagged commented on May 21, 2024

Are you open to PR's to help with the code documentation? If so, I suggest we co-coordinate efforts by raising issues for each class or related group of classes. Obviously your README file is a great source to pull from, beats starting from scratch!

from language-ext.

louthy avatar louthy commented on May 21, 2024

Are you open to PR's to help with the code documentation?

Absolutely. As I say I'm trying to cover as much as I can with my limited time, but any help with that process would be appreciated.

from language-ext.

Jagged avatar Jagged commented on May 21, 2024

I'll start on docs for Fold and Reduce next, etc, it's about time I actually got my head around these!

from language-ext.

louthy avatar louthy commented on May 21, 2024

@Jagged RE: fold and reduce, take a look at List.cs, both fold and reduce are documented there.

For a background, fold and reduce are essentially how you do loops with aggregate state in functional programming.

Take this imperative example for finding the sum of a list of integers:

    var total = 0;
    var list = List(1,2,3,4,5);
    foreach(var item in list)
    {
        total += item;
    }

Trying to do something as simple as summing a list of values functionally is difficult if you can't carry a state value and you're trying to program with expressions:

    var res = list.Select( x => .... what goes here? );

That's what fold is for. In a fully functional implementation it would look like this:

    S Fold<S,T>(S state, Func<S,T,S> folder, Lst<T> list) =>
        list.Count == 0
            ? state
            : Fold(folder(state,list.Head()), folder, list.Tail());

That's why it's the functional way of doing loops, because there is no loop (and therefore no mutation of local variables), just a single recursive expression. If there are items in the list, it takes the current state and the head item and passes it to the folder function, the result becomes the new state, then it recalls itself with the new state and the tail of the list. This happens recursively until there are no items left to process, at which point it returns the aggregate state value.

reduce is fold, except it doesn't take an initial state value, it uses the first item in the list as the initial state value (and therefore can't work with empty lists).

The functional world doesn't really do design-patterns, but if anything is a design-pattern then it's fold. It's such a common theme, that when you build a recursive expression, 9 times out of 10 you'll realise it can be a fold. So after a time you end up going straight to fold rather than building recursively.

There is also the issue that C# will happily blow up the stack after about 10,000 recursive calls. So the Language-Ext implementation of fold is actually imperative:

    public static S fold<S, T>(IEnumerable<T> list, S state, Func<S, T, S> folder)
    {
        foreach (var item in list)
        {
            state = folder(state, item);
        }
        return state;
    }

So back to the summing integers example:

    var list = List(1,2,3,4,5);
    var total = fold(list, 0, (s,x) => s + x);

Now imagine doing that for calculating the product:

    var list = List(1,2,3,4,5);
    var total = fold(list, 0, (s,x) => s * x);

It wouldn't work, because the first time the folder function is called it will have an s of 0. So the result will always be zero. But if you used reduce instead:

    var list = List(1,2,3,4,5);
    var total = reduce(list, (s,x) => s * x);

Then s will have an initial state of 1 because it will take the first item from the list.

You may now wonder why fold is part of Option, Either, etc. However if you think of Option as a list that has either 0 or 1 items in it, then you can see how you can treat Option, Either, List, Set, Try, etc. the same and provide a common interface.

from language-ext.

Jagged avatar Jagged commented on May 21, 2024

Paul, thank you for that detailed description and background, the scary thing is I think I understood it! Nicely explained!

Fold is for combining values into another value (think cake mixture and adding extra ingredients).
Reduce is for reducing down to a single value (think boiling down to a concentrate).

That's assuming I got it correctly? (and if the lame lay-person examples make sense!)

from language-ext.

louthy avatar louthy commented on May 21, 2024

They're both for aggregating a value. It's only the start condition that's different.

Here's some food for thought: many of the common list processing functions represented as folds...

public static EnumerableExt
{
    public static int Sum(this IEnumerable<T> self) =>
        self.Fold(0, (s,x) => s + x);

    public static int Count(this IEnumerable<T> self) =>
        self.Fold(0, (s, _) => s + 1);

    public static IEnumerable<U> Map<T,U>(this IEnumerable<T> self, Func<T, U> map) =>
        self.Fold(List.empty<U>(), (s,x) => s.Add(map(x)));

    public static IEnumerable<T> Filter<T>(this IEnumerable<T> self, Func<T, bool> pred) =>
        self.Fold(List.empty<T>(), (s,x) => 
            pred(x) 
                ? s.Add(map(x)) 
                : s
                );

    public static Unit Iter<T>(this IEnumerable<T> self, Func<T, Unit> action) =>
        self.Fold(unit, (_, x) => action(x) );

    public static bool Exists<T>(this IEnumerable<T> self, Func<T, bool> pred) =>
        self.Fold(false, (s, x) => s 
            ? true
            : pred(x)
            );

    public static bool ForAll<T>(this IEnumerable<T> self, Func<T, bool> pred) =>
        self.Fold(true, (s, x) => s 
            ? pred(x)
            : false
            );
}

from language-ext.

Jagged avatar Jagged commented on May 21, 2024

They're both for aggregating a value. It's only the start condition that's different.
Yep, I get that. It would be interesting to find the roots of the naming of those two methods, to see what inspired them to be called fold and reduce .. =)

Darnit, I'm finding it easier and easier to read those! I recently took a look at some F# code and it made me realise how much C# has already started the transition with Linq and similar, getting people used to piping results from function to function to function. OK, the syntax is different, but the flow is much the same now. Lots of small reusable functions.

I can't help but notice a huge amount of repetition when I searched for Fold (etc). I wonder if there's a way to get the compiler to help us here. T4 templates? Marking classes as IFoldable etc and using extensions?

When looking at some of the Fold comments I found two distinct styles: (1) what it does, and (2) how it does it, in quite a bit of detail. The latter doesn't work that well with intellisense.

Perhaps we could move the How under remarks and put the What under summary?

What would you like to do there?

from language-ext.

louthy avatar louthy commented on May 21, 2024

Yep, I get that. It would be interesting to find the roots of the naming of those two methods, to see what inspired them to be called fold and reduce

I guess catamorphism was too long ;)

'Piping' and 'LINQ' are two subtly different things. 'Piping' AKA function composition is passing the result of one function to single input parameter of the next.

We have the compose function for that. To see an example of this in the Samples:
https://github.com/louthy/language-ext/blob/master/Samples/UnitsOfMeasureSample/BallProcess.cs#L50

    compose(
        Accelerate(msg.Time),
        Move,
        FloorCeilingDetection,
        WallsDetection,
        SetTime(msg.Time));

This is what that world look like without the compose:

    state = SetTime(msg.Time, WallsDetection(FloorCeilingDetection(Move(Accelerate(msg.Time, state)))));

You could expand it to make it a touch clearer:

    state = Accelerate(msg.Time, state);
    state = Move(state);
    state = FloorCeilingDetection(state);
    state = WallsDetection(state);
    state = SetTime(msg.Time, state);

You may also notice that those methods could be written as extension methods for the state type, and you'd end up with this:

    state.Accelerate(msg.Time);
         .Move();
         .FloorCeilingDetection();
         .WallsDetection();
         .SetTime(msg.Time);

LINQ deals with monads and uses the monadic bind function. It is also composition, but it can work with the values wrapped within the monad and put them back in a new monad before returning. That allows the behaviour of the monad to be injected ("programmable semi-colons" is the best description I've heard for them). For example the bindfunction for Option acknowledges the Some / None states, and doesn't perform the bind operation on None values.

    Option<U> Bind<T,U>(Option<T> option, Func<T, Option<U>> bind) =>
        option.IsSome
            ? bind(option.Value)
            : None;

You can then do monadic binding like so:

    Option<int> x = Some(10);
    Option<int> y = Some(10);
    Option<int> res = Bind( x, a => Bind(y, b => Some(a + b) );  // Some(20)

But that's obviously very clunky and that's where LINQ comes in. It does monad composition gracefully:

    Option<int> x = Some(10);
    Option<int> y = Some(10);
    Option<int> res = from a in x
                      from b in y
                      select a + b;

I can't help but notice a huge amount of repetition when I searched for Fold (etc). I wonder if there's a way to get the compiler to help us here. T4 templates? Marking classes as IFoldable etc and using extensions?

I'd rather not. Fold doesn't change, and neither does the definition of a lot of functions in this library. So whilst there may be an upfront typing cost, there isn't an ongoing project management cost.

Perhaps we could move the How under remarks and put the What under summary?

Sure. Keep the wording, but splitting them is OK.

from language-ext.

Jagged avatar Jagged commented on May 21, 2024

cata-what-ism ???? lol! I'd never remember how to write that!

I like where bind is going there, and the use of LINQ. There are some imperative bits in the code that are screaming out to be made functional, eg TryOption.Subtract. I'd love to see what milage bind might offer, if for no reason to see how functional coding could be used in a situation like that.

edit: Based on what you said earlier, would the following be equivalent?

    public static TryOption<T> Append<T>(this TryOption<T> lhs, TryOption<T> rhs) => () =>
    {
        // imperative
        var lhsRes = lhs.Try();
        if (lhsRes.IsFaulted || lhsRes.Value.IsNone) return lhsRes;
        var rhsRes = rhs.Try();
        if (rhsRes.IsFaulted || rhsRes.Value.IsNone) return rhsRes;
        return TypeDesc.Append(lhsRes.Value, rhsRes.Value, TypeDesc<T>.Default);

        // or bind
        return bind(lhs, l => bind(rhs, r => TypeDesc.Append(l, r, TypeDesc<T>.Default)))

        // or LINQ (is there a cleaner way without Invoke?)
        return (from l in lhs
                    from r in rhs
                    select TypeDesc.Append(l, r, TypeDesc<T>.Default)).Invoke();    
};

edit2: I had a closer look at Bind for TryOption, it definitely looks like it'll do it. Trying to follow the LINQ version thru the code is a little harder!

from language-ext.

louthy avatar louthy commented on May 21, 2024

Mostly if I use imperative code it's for performance or readability reasons. I will always prefer expressions over statements, but sometimes the cost of LINQ or closures is too great. Operators are a good example where most consumers of the code would expect you to be as optimal as possible (that's why there's so much type-caching).

Actually the behaviour of Append is wrong. It needs to be more consistent with the Option<T>.Append in that:

    None + Some(rhs) = Some(rhs)
    Some(lhs) + None = Some(lhs)
    Some(lhs) + Some(rhs) = Some(lhs + rhs)

For TryOption<T> I think it should be:

    None + Succ(rhs) = Succ(rhs)
    Succ(lhs) + None = Succ(lhs)
    Succ(lhs) + Succ(rhs) = Succ(lhs + rhs)
    Fail + None = Fail
    None + Fail = Fail
    Succ + Fail = Fail
    Fail + Succ = Fail
    Fail + Fail = Fail

That also means it's not bind. I'll fix that up now.

from language-ext.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.