-
-
Notifications
You must be signed in to change notification settings - Fork 414
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
Would be great if code would be documented :) #31
Comments
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.
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. |
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! |
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. |
I'll start on docs for Fold and Reduce next, etc, it's about time I actually got my head around these! |
@Jagged RE: fold and reduce, take a look at List.cs, both For a background, 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
The functional world doesn't really do design-patterns, but if anything is a design-pattern then it's 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 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 var list = List(1,2,3,4,5);
var total = reduce(list, (s,x) => s * x); Then You may now wonder why |
Paul, thank you for that detailed description and background, the scary thing is I think I understood it! Nicely explained!
That's assuming I got it correctly? (and if the lame lay-person examples make sense!) |
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
);
} |
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? |
I guess '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(
Accelerate(msg.Time),
Move,
FloorCeilingDetection,
WallsDetection,
SetTime(msg.Time)); This is what that world look like without the 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.Accelerate(msg.Time);
.Move();
.FloorCeilingDetection();
.WallsDetection();
.SetTime(msg.Time); LINQ deals with monads and uses the monadic 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'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.
Sure. Keep the wording, but splitting them is OK. |
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! |
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 None + Some(rhs) = Some(rhs)
Some(lhs) + None = Some(lhs)
Some(lhs) + Some(rhs) = Some(lhs + rhs) For 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 |
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.
Mentioning linq equivalents in a consistent pattern would be nice:
MethodName - what it does (Linq equivalent or No Linq equivalent)
By the way, nice work. I got into this by trying to reduce cyclomatic complexity => search for option/maybe C# implementation.
http://www.codinghelmet.com/?path=howto/reduce-cyclomatic-complexity-option-functional-type
Another aspect regarding documentation, maybe add a link in docs to your comments regarding how this project relates to akka.net. My first thought when reading about implementing actor model was why not use akka instead, and I later found the git issue discussing this.
The text was updated successfully, but these errors were encountered: