-
Notifications
You must be signed in to change notification settings - Fork 59
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
Improve documentation #36
Comments
Hi @mateusfccp! I completely agree. You are not the first one to suggest this and I believe that at least some basic level of documentation would make the library much more approachable. I'm not even going to try to come up with any excuses for the current sorry state of the documentation :-) Let's just say I would welcome any help with open arms! If you or others are willing to contribute, how would you feel about starting with a PR based approach and switching to collaborator mode once we've gotten our feet wet? |
Hi, @spebbe I'm fine with your suggestion. I don't have a lot of time, but I'm willing to gradually help with documentation whenever I can. If other people would join me, any help is appreciated. May we use this issue to track the work? |
Sure! Also, no pressure – any improvement whenever you have the time is appreciated. |
As another FP fan, I'd like to help a little as well. Would it be convenient to create a docs branch for this work that could be merged into master periodically until we get some momentum? Or should we just submit PRs against master? |
@deanriverson, I appreciate your help. If you feel to, we may initially work on a forked branch of ours. I already started to work on some basic documentation on less complex classes, like We may then set up some general guidelines for the documentation to be consistent. By now, I'm using the Effective Dart guidelines, as it's a good idea to be in conformance with the community, but we may also define some more specific guidelines if we like to. |
Agreed, working on a forked repo is the best way to go. I also like the plan of using the Effective Dart guidelines as a baseline and layering more specific guidelines on top if we need to. Looking forward to seeing your initial work! |
Well, here is my initial attempt ( Some principles I tried to acomplish:
For now, I left overriden methods ( What do you think about this approach? |
@mateusfccp I see that you mentioned me (but meant to mention dean). I still would like to give my retrospective opinion about the lack of documentation since I did spend quite some time learning FP mainly by digging through dartz. IMHO dartz is not lacking documentation. Each element in itself is not complicated to grasp. The hardest part is getting a feel for the full picture. Documenting monoids and monads feels to me like teaching a programmer, while he is programming, what classes are. The biggest problem that new developers have with fp is that they think that it can't be used to solve real world problems. The example folder in dartz is invaluable. It teaches techniques that are eye opening to someone who is used to OOP and that is what I believe dartz needs more of. I don't mean to offend you or your efforts, but everytime I tried to convince someone to take a look at fp it never failed because of the lack of documentation but because they didn't see why they would need it. Explaining what a lens (or any other concept for that matter is) is not enough to give them an intuitive understanding for them. They need to see FP in action. |
@modulovalue Although I agree partly with you, I don't think this is the point of the documentation. As a functional programmer myself, I found the lack of documentation a little frustrating when started with dartz. For example, I only realized the fact that There's also the case where a person is still learning FP and know some concepts but not all of them. In this cases, a documentation helps him to learn a little about the concept and its practical utilization, and if they like to, they may learn more about the subject by itself. |
I definitely agree with @modulovalue that explaining how each of the types can be used is important. But I think even the documentation that @mateusfccp did for a type as simple as Unit shows how important source code documentation can be. It's not that Unit is a difficult concept to grasp, it's more about getting new users (new to FP as well as new to dartz) comfortable with the library and its philosophy. If you're an old hand at FP (I'm sure they exist somewhere) then you know right away that this is the Unit you're used to, but with maybe a few differences. For a newbie, it makes the library infinitely more approachable. |
@mateusfccp That is a great start! Exactly what I was picturing. |
@deanriverson There's details pending as stated by @modulovalue on the commit, but I'm glad you liked it. I'm going to Semigroup now. Feel free to give suggestions. |
Off to a great start! Thank you, people! @modulovalue, your reasoning rings true to me too. I believe usage examples are the best (and possibly only) way to convey how to "do" FP. On the other hand, I think that some light documentation on the basic purpose and potential quirks of individual types can't hurt either. I've recently seen a surprising amount of people using parts of Haskell people on the other hand might appreciate some hint that While I like the |
@spebbe I though a little about this. This is why I gave a more tangible explanation with "This means that". Maybe we can switch positions and place the "easier" explanation before and throw the more mathematical explanation on the Additional Details section... What do you think? |
Even so, I don't think it's easy to do this in classes that are by itself the concreteness of mathematical terminology, like Monoid, Semigroup, Functor... How could we make it tangible to people who don't really know algebraic and category terms? |
Here's another idea, wouldn't it perhaps make more sense to start at the readme level since most people get to see the readme first? That would also be a great way to solve this problem
And a second completely different suggestion for you @mateusfccp. Of course, it all depends on how far you're willing to go but I personally believe that examples and blog posts are the way to go if you want to help the most people. We have a great community over at r/FlutterDev and FlutterCommunity on Medium. I think they would really appreciate that a lot and it would expose dartz to people who may have never considered using FP concepts before. Edit: here's an example of a readme that I believe would be a great fit for dartz extended_math |
@mateusfccp, Agree. When possible to explain something without category theory, it maybe should be done at the top and with examples. |
All great ideas. I'm not great on category theory, so I personally liked the balance @mateusfccp struck on the Unit documentation. A little light theory coupled with a little bit of exposition in the form of "this means that..." was just right. I definitely agree that heavy theory discussions should be avoided in favor of links to the already existing and plentiful resources. This means we have the opportunity to link to the articles that best explain the concepts used in dartz -- a curated list, so to speak. I also second @modulovalue's thoughts on placing a high priority on the README as well as blog posts and other outreach. I think it's highly effective to link blog posts from a project's README. They are usually more useful to newcomers than just diving into the example code unless the example code is simple and extremely well commented. It's funny that using Option instead of Maybe was mentioned because that definitely threw me when I was first looking at dartz since finding a Dart Maybe type was what motivated my search for a FP library. So I can relate to that! |
@spebbe I'm going to try this inverted approach, and remove teory details to a minimum so teory proficients will know what this does means with just a glance. Plus, add reference links to in-dept explanation. For classes like Semigroup, I'm going to use simple on-line statements, like "A structure with an associative binary operator", plus the reference links. What do you think about this approach? @modulovalue Maybe it's a valid alternative that may also improve the situation of FP on dart/flutter. Sadly, I don't have the time and energy now to work in this kind of bigger project. I agree that this is in some aspect more valuable, but it also demands more responsibility and dedication, that I unfurtunately don't have currently... |
Here are some initial thoughts on improving the README:
What else? |
@deanriverson: love it! The main reason I still have for maintaining Dart 1 compatibility is that the test suite is running with a "fake" property testing engine on Dart 2, meaning that the test suite isn't as confidence building as it is on Dart 1 yet. As soon as I can get my hands on/write a real engine that works with Dart 2, I'll ditch Dart 1! I'm really excited about the high ambitions here, but I want to stress that I am grateful for any contribution/involvement. Let's keep the good ideas flowing, but please don't feel that you are being coerced into doing more/other work than you feel comfortable with. |
@spebbe I'm happy to take on the README work. I think we can draw some inspiration from scalaz and some of the other popular FP libraries. I'm working my way through the FP in Scala book (really great book, thanks for the recommendation!) and I'm hoping to take inspiration from it for some examples to use in a Quick Start section for the dartz README. I'll also go back through some of the other tutorials and books I've liked, searching for some useful but easy-to-understand examples of the type classes. If anyone has suggestions for Quick Start examples or of their favorite FP links for the Resources section, I'd love to hear them! |
I have a little question regarding By now, I documented like this: /// A [Semigroup] whose [append] always return the first element.
final Semigroup FirstSemigroup = semigroup((a1, a2) => a1);
/// Creates a [Semigroup] on `A` whose [append] always return the first element.
Semigroup<A> firstSemigroup<A>() => cast(FirstSemigroup); If you are going to work on the readme, please, feel free to send me the status so I can track it in the original post, like I'm doing with in-code documentation! |
@mateusfccp, crap, I should have warned you! It is probably more fruitful to work based on the 0_9_0_wip_2 branch. I've been procrastinating merging it, but it has been more or less ready for a while now. The definitions for This specific pattern allowed a singleton instance of |
@spebbe I rebased to |
Any update on this. I want to use this package but I can't find any getting-started documentation. |
I know it's unfortunate @pedromassango but because naming is pretty much the same across languages, I'd recommend you to check out Scala Cats and Kotlin Arrow docs. |
@pedromassango I'm working only on in-code documentation, so I can't really help you with this. I don't know how is things at @deanriverson side. However, you can check other sources, like @ResoDev suggested. |
Same here, lack of example codes creates a huge barrier from learning this awesome lib. |
Even a few simple examples of Option, Either, State, Tuple, Free, Lens would help us a lot of learning this great library. |
@PandaGeek1024 Although there's a While we are at it, do you have any specific question? I would be glad to help you understand a little more how to use this package on your code! |
@mateusfccp I have never learned scala before, so I went to do a bit of reading about it. Although I do have a rough idea now, when it comes to actual coding I still don't know where to start. I have read the example code and the source code of the package but it is quite hard to understand how it works (due to my lack of scala knowledge). As a complete newbie, I would like to know what are the commonly used monads that we can possibly use in flutter? I have been using Either (thanks to resocoder) in my project (and it is really handy), but that's about it because I don't know much about other monads. Would really appreciate if you can share your knowledge about it, and alongside with it, any simple examples are even better :) |
@PandaGeek1024 Although dartz is inspired by scalaz, you don't have to know Scala to understand it. Both are, actually, implementations of the functional paradigm. Of course, knowing Scala will help you, but you can focus on the functional concepts instead. Also, there are a plenty of other FP languages that may help you on your studies. One of the most common is Haskell, as it's purely functional and has a strongly theory based construction. About the monads, the key point to them is composition. Monads composition often happen with the methods For starting, I think the most common (and simple) monads are I won't explain everything here nor put examples here as this is not actually the purpose of this issues, but pm me if you feel like. |
While making the documentation of Option, I noted that there was both What is the difference between Why there are both functions in Option? |
@mateusfccp: they are both just aliases for monadic bind (aka |
Just released 0.9.0. All merged to master, so feel free to rebase! |
Is this a not documentation related release? I can't see that changes. |
Follow this to learn the how this package works, this one is for Kotlin but the concepts are the same: #36 (comment) |
Thanks for the fantastic library! One thing I ran into, and I'm sure it's just a limitation of the Dart type system, but I'm having trouble figuring out how to compose multiple Futures in a concise way. I hate to clutter this issue with my sad code, but @mateusfccp mentioned monad composition. I know there's a FutureMonad class in dartz, but I can't quite figure out what to do with it.
|
Hi @ibcoleman! Thanks – glad you like it! Although maybe slightly off topic for this ticket... :-) ...first off, here's a re-formulation of your code that requires less explicit typing: Future<Either<Exception, String>> getHeader() async => right("header");
Future<Either<Exception, String>> getBody(String header) async => right("$header:body");
Future<Either<Exception, String>> getFooter(String body) async => right("$body:footer");
Future<Either<Exception,String>> fullValueFE() async {
final header = await getHeader();
final withBody = (await header.traverseFuture(getBody)).flatMap(id);
final withFooter = (await withBody.traverseFuture(getFooter)).flatMap(id);
return withFooter;
}
fullValueFE().then((it) => it.fold(
(err) => fail("Shouldn't Be Here"),
(body) => expect(body, equals("header:body:footer")))); Ideally, there should be a While monadic values do compose through final M = EvaluationMonad<Exception, Unit, Unit, Unit>(UnitMi);
final Evaluation<Exception, Unit, Unit, Unit, String> getHeader = M.pure("header");
Evaluation<Exception, Unit, Unit, Unit, String> getBody(String header) => M.pure("$header:body");
Evaluation<Exception, Unit, Unit, Unit, String> getFooter(String body) => M.pure("$body:footer");
final Evaluation<Exception, Unit, Unit, Unit, String> fullValueE =
getHeader.bind(getBody).bind(getFooter);
final result = await fullValueE.value(unit, unit);
result.fold(
(err) => fail("Shouldn't Be Here"),
(body) => expect(body, equals("header:body:footer"))); The types are a bit of a mouthful and look a bit silly when not using the full power of You'll find lots of yummy methods on Hope that helps! |
Thanks, that was very helpful! |
Hi! I've been messing around with EvaluationMonad and Evaluation for a few days now. One thing I just can't seem to figure out: In my code, it's obviously common that I have a function that returns a I looked through evaluation_test.dart in the repository, and there's a fairly clear example of liftFuture, and a fairly clear example of liftEither. But they're both completely distinct which makes me think there's no way out of the corner I've painted myself into? :) |
@spebbe I never, however, had heard of |
@mateusfccp It's hard to come up with a self contained use case, but they are sometimes useful for injecting "outside knowledge" into a context. Here's a relatively isolated example of |
@modulovalue "Explaining what a lens (or any other concept for that matter is) is not enough to give them an intuitive understanding for them. They need to see FP in action." Yes! As a newbie programmer trying to understand and integrate FP with Dart, more examples would really be helpful. I watched The Functional Programmer's Toolkit - Scott Wlaschin, read some of the recommended Scala book and the Category Theory writing, but I still have trouble applying it in Dart. Also I believe what would be helpful are use-case bullet points without actual code. Sort of a high-level steps guide drawing from your experience as to where you use FP instead of object-oriented. Where do you often use FP in Dart? Or if your are performing an HTTP call, instead of setting an interface for error, use the Either<Success, Failure>, then Left/Right, Fold, etc, to extract the value, and so on. Or if I am setting-up a persistent database using Hive - init, registerAdapter, openBox, get/save data - how would you do it with FP principles. Is it possible? Does it require workarounds because of Dart being OO? |
What is the state of the code documentation? I installed the null-safety version and can't see anything! Maybe I'm looking in the wrong place or the changes haven't been merged? I'm learning functional programming and it's difficult finding sufficiently thorough Dart examples at the right level. I've ended up looking at other languages. "Professor Frisby" is good. He has a free book. I'm currently following his video, Professor Frisby Introduces Composable Functional JavaScript. The video starts by introducing an imperative function: String nextCharForNumberStringImp(String s) {
var trimmed = s.trim();
var number = trimmed.toInt();
var nextNumber = number + 1;
var char = String.fromCharCode(nextNumber);
return char.toLowerCase();
} (This and the following are my Dart versions of his JavaScript code.) He then creates a "box". My Dart version is: class Box {
final x;
Box(this.x);
Box map(f) => Box(f(x));
dynamic fold(f) => f(x);
@override
String toString() => x;
} and converts the above imperitive function into: String nextCharForNumberStringFP(String s) =>
Box(s)
.map((s) => s.trim())
.map((s) => int.parse(s))
.map((i) => i + 1)
.map((i) => String.fromCharCode((i)))
.fold((s) => s.toLowerCase()); In the video, a 'student' points out that I'm still working my way through the video but I thought there must be a Dart library that has Looking at the Dartz library, I can't see how I would use it in this context. It would be really great if there were Dartz examples of the things in the video. That way people could learn functional programming from an engaging presenter and apply it to Dart. The presenter goes on to use immutablejs and his own extension. Both have plenty of examples in JavaScript. Here's the JavaScript example of applicative (to get list comprehensions) from his extension's Github page: List.of(x => y => x + y)
.ap(List.of('a', 'b', 'c'))
.ap(List.of('+', '-'))
// List('a+', 'a-', 'b+', 'b-', 'c+', 'c-') What's the Dartz equivalent?! The current Dartz examples provided are too advanced for me. I guess they're for people who know functional programming already? I'd be happy to contribute but at the moment I can't even see how to get the equivalent of |
Hello @Praful Sadly, I have to say that my advice for you would be to go as far away from javascript, or any other untyped language, as you can. There's a reason why companies have collectively spent billions trying to replace it. Your example can be rewritten to be 'functional' by making use of the final keyword, which tells the compiler that those variables cannot be mutated after a value has been assigned to them. String nextCharForNumberStringImp(String s) {
final String trimmed = s.trim();
final int? number = int.tryParse(trimmed);
if (number == null) {
/// TODO handle error.
return "Not a number";
} else {
final int nextNumber = number + 1;
final String char = String.fromCharCode(nextNumber);
return char.toLowerCase();
}
} |
Hi @Praful! As @modulovalue mentioned, That example aside, if you want to map the text you are following to // NOTE: The unnecessary functorial style below is an educational example only.
String nextCharForNumberStringFP(String s) =>
optionOf(s)
.map((s) => s.trim())
.map((s) => int.parse(s))
.map((i) => i + 1)
.map((i) => String.fromCharCode((i)))
.fold(() => "", (s) => s.toLowerCase()); Just remember that this doesn't really do anything besides obfuscating the code a bit ;-) The point of identity constructs is that they don't provide any effects besides naming. The code above is actually less "functional" than the code in @modulovalue's post above, since it can still throw exceptions. As for the list applicative example, this is one way of expressing it in ilist(['+', '-'])
.ap(ilist(['a', 'b', 'c'])
.ap(ilist([(x) => (y) => x + y]))) |
I think what @Praful want to achive is making his own custom monad which is called Which I understand it is quite hard to achive / understand with using dartz. For me I can not understand how to translate this scala code into dartz at the moment. case class User(email: String, username: String, password: String = "")
trait UserService[C[_]] {
def register(email: String, password: String, username: String): C[Option[User]]
def login(email: String, password: String): C[User]
} so then I can implement UserService using |
Thanks @modulovalue for your updated example. You probably know that the type declarations are optional because Dart works out the types and, if it doesn't, it will let you know :) I also learnt that the @spebbe - thanks for the two examples. The code I posted is from the first lesson in the tutorial - so definitely for illustrative purposes. I was curious about your remark about the example ( Either fromNullable(x, msg) => x != null ? Right(x) : Left(msg); Using this, I can now have a null check, making it functionally equivalent to @modulovalue's version: Either nextCharForNumberStringFP3(String s) =>
Right(s).map((s) => s.trim()).fold(
(error) => error,
(s) => fromNullable(int.tryParse(s), 'not a number')
.map((i) => i + 1)
.map((i) => String.fromCharCode((i)))
.map((s) => s.toLowerCase())); I don't know if this is the "functional programming" way of doing this. The tutorial says the advantages of this composition technique are that the data flow is clearer, and each expression manages its own state so there are no variables, as there are in the imperative version. And if we had any nested expressions in the original, such as
the chained example removes the nesting, which is clearer. These seem to me advantages but I have only started looking at functional programming. Having written the dartz library, you are an expert. What do you think of these advantages? Also, do you know when the documentation changes raised in this issue will be merged into a release? Edit: I forgot to ask is what @apiep asks (making a custom monad for |
Yes exactly, but your Box translation to Dart is untyped. The type system needs more information to be able to infer useful types. This is what a type safe version of Box would look like: class Box<T> {
final T x;
const Box(this.x);
Box<U> fmap<U>(U Function(T) f) => Box(f(x));
U fold<U>(U Function(T) f) => f(x);
@override
String toString() => x.toString();
}
It won't, it will infer the types to be dynamic. You need to add these analyzer settings to your analysis_options.yaml file to let the analyzer know that you reeally want it to let you know! |
My Box class was just a way of following the tutorial (i.e. not to be critiqued:)) because my assumption/hope was that a functional library would have what Box represented or that I could create a custom monad as @apiep suggested. But thank you for providing a typed version.
I was not aware of that. Adding those options produced interesting results in some code I had :) |
@spebbe I like the positive and understanding attitude you show in this issue. It is in stark contrast to functionaljava/functionaljava#394 . I came here looking for a dart
After reading that, I realized that |
I could be wrong, but I think you want leftMap:
I think the idiomatic way of (eventually) dealing with exceptions via Either would be using
Sorry if I misunderstood your question. |
I've been kind of cross-referencing Manning's Functional Programming in Scala, and the (excellent) online documentation for the Kotlin Arrow (https://arrow-kt.com) library. Sometimes it's a bit like translating the Analects with only a vague notion of Confucianism and a menu from a Chinese takeout restaurant. :) Also, came across this great @spebbe video on YouTube: https://www.youtube.com/watch?v=90jq6HAb2gw. Wish it was about 4 hours longer, though! |
@ibcoleman Really glad you liked the video! @mleonhard Thank you for your kind words and suggestions! The lack of |
I love this package and use it wherever I can, as I love functional programming. The only thing that bothers me a little is the lack of documentation.
Would'nt it be a nice idea to document the code? I know it's a annoying task, but the results are gratificating, so I'm willing to help. I have a little knowledge on category theory that may help, although it's not a deep knowledge. However, I feel like helping on the documentation will also help me improve my knowledge and skills on FP/CT.
You may set me as collaborator, or we may work simply with gradual PRs, or you may ever decline my proposal.
Thanks, in advance.
Documentation status
In-code documentation
streaming
conveyor.dart
io.dart
pipe.dart
source.dart
tee.dart
text.dart
unsafe
each.dart
io.dart
applicative.dart
applicative_plus.dart
avl_tree.dart
builtins.dart
dual.dart
either.dart
endo.dart
eq.dart
evaluation.dart
foldable.dart
free.dart
free_composition.dart
function.dart
(in progress)functor.dart
future.dart
id.dart
ihashmap.dart
ilist.dart
imap.dart
io.dart
iset.dart
ivector.dart
lens.dart
list.dart
monad.dart
monad_catch.dart
monad_plus.dart
monoid.dart
(in progress)option.dart
order.dart
plus.dart
plus_empty.dart
semigroup.dart
(in progress)state.dart
task.dart
trampoline.dart
traversable.dart
traversable_monad.dart
traversable_monad_plus.dart
tuple.dart
unit.dart
(in progress)The text was updated successfully, but these errors were encountered: