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

Improve documentation #36

Open
51 tasks
mateusfccp opened this issue Feb 10, 2020 · 57 comments
Open
51 tasks

Improve documentation #36

mateusfccp opened this issue Feb 10, 2020 · 57 comments

Comments

@mateusfccp
Copy link

mateusfccp commented Feb 10, 2020

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)
@spebbe
Copy link
Owner

spebbe commented Feb 10, 2020

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?

@mateusfccp
Copy link
Author

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?

@spebbe
Copy link
Owner

spebbe commented Feb 10, 2020

Sure! Also, no pressure – any improvement whenever you have the time is appreciated.

@deanriverson
Copy link

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?

@mateusfccp
Copy link
Author

@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 Unit, Semigroup and Monoid. I'm going to push it tomorrow so you all can see if you like the approach I'm using by now.

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.

@deanriverson
Copy link

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!

@mateusfccp
Copy link
Author

mateusfccp commented Feb 11, 2020

Well, here is my initial attempt (unit.dart ):
https://github.com/mateusfccp/dartz/blob/master/lib/src/unit.dart

Some principles I tried to acomplish:

  • I tried to be straight and clear;
  • I tried to provide a minimal and practical example;
  • I tried to left additional not so important details under "Additional details".

For now, I left overriden methods (toString, zero, append) to inherit its documentations from its parents, but they can of course be rewritten if needed.

What do you think about this approach?
@spebbe @deanriverson

@modulovalue
Copy link
Contributor

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

@mateusfccp
Copy link
Author

mateusfccp commented Feb 11, 2020

@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 | could be used for getOrElse after looking at the source code.

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.

@deanriverson
Copy link

deanriverson commented Feb 11, 2020

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.

@deanriverson
Copy link

@mateusfccp That is a great start! Exactly what I was picturing.

@mateusfccp
Copy link
Author

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

@spebbe
Copy link
Owner

spebbe commented Feb 11, 2020

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 dartz, mainly Either, that don't seem to have any desire to go all-in on FP. Either and a couple of other structures are relatively useful in isolation and I think it could be nice to lower the threshold and clear up some confusion about why there is no get, what you should use instead, etc.

Haskell people on the other hand might appreciate some hint that Option is what they would normally call Maybe, etc. ok, a bit of a stupid example, i know, but you get what i mean ;-)

While I like the Unit documentation, I'm not sure who benefits from the category theoretical information in it. People who have studied category theory might just find it obvious and redundant, while people who haven't will probably find it incomprehensible...? I'm very torn about this though... What do you think?

@mateusfccp
Copy link
Author

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

@mateusfccp
Copy link
Author

mateusfccp commented Feb 11, 2020

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?

@modulovalue
Copy link
Contributor

modulovalue commented Feb 11, 2020

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

Either and a couple of other structures are relatively useful in isolation and I think it could be nice to lower the threshold and clear up some confusion about why there is no get, what you should use instead, etc.

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

@spebbe
Copy link
Owner

spebbe commented Feb 11, 2020

@mateusfccp, Agree. When possible to explain something without category theory, it maybe should be done at the top and with examples.
For pure category theoretical structures like the ones you mention, I'm wondering if we might just point interested readers to some of the great learning resources out there (Chiusano&Bjarnason, Milewski, Lipovača, typeclassopedia, etc)? Repeating the standard definitions and laws will probably not help the FP-curious and will be old news to the seasoned FP crowd.

@deanriverson
Copy link

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!

@mateusfccp
Copy link
Author

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

@deanriverson
Copy link

Here are some initial thoughts on improving the README:

  1. I think we can mainly focus on replacing the bullet list of features with some details and simple examples
  2. Move Dart 1 features to a separate section later in the document. I'm assuming the Dart 2 is the path forward and will be more relevant to the vast majority of current and future users.
  3. For each of the major types, give a brief description of what it does and a simple motivating example for why you'd want to use it.
  4. Give examples of common FP idioms in dartz like currying and function composition. Focus on the things everyone will need to do.

What else?

@spebbe
Copy link
Owner

spebbe commented Feb 13, 2020

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

@mateusfccp mateusfccp changed the title Provide in-code documentation Improve documentation Feb 13, 2020
@deanriverson
Copy link

deanriverson commented Feb 13, 2020

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

@mateusfccp
Copy link
Author

mateusfccp commented Feb 13, 2020

@spebbe

I have a little question regarding FirstSemigroup and firstSemigroup() usage. What is the difference in the use-case of these? In my understanding of the code the only difference is that one is typed and the other is dynamic. Am I missing something?

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

@deanriverson

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!

@spebbe
Copy link
Owner

spebbe commented Feb 13, 2020

@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 firstSemigroup and a couple of others look different on that branch.

This specific pattern allowed a singleton instance of FirstSemigroup to be used for any A. This worked as a neat, safe optimization on Dart 1 for "empty" types like FirstSemiGroup, Nil and None, but DDC will notice it and complain although it should in practice be perfectly safe. Compare it with types parameterized with Nothing in scala.

@mateusfccp
Copy link
Author

@spebbe I rebased to 0_9_0_wip_2 with no problems and I'm going to proceed with the work.

@pedromassango
Copy link

Any update on this. I want to use this package but I can't find any getting-started documentation.

@ResoDev
Copy link

ResoDev commented Mar 7, 2020

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.

@mateusfccp
Copy link
Author

mateusfccp commented Mar 7, 2020

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

@bounty1342
Copy link

Hi,

Followed @ResoDev video and successfully used it on my project. 👍
It feels like I'm just scratching the surface and there is much more to discover.

I keep an eyes on this thread and will be satisfied with either a doc or a new in depth video 😄

@PandaGeek1024
Copy link

Same here, lack of example codes creates a huge barrier from learning this awesome lib.

@PandaGeek1024
Copy link

Even a few simple examples of Option, Either, State, Tuple, Free, Lens would help us a lot of learning this great library.

@mateusfccp
Copy link
Author

@PandaGeek1024 Although there's a example folder, I agree that it's not as comprehensive as it could be, and is somewhat a little confusing. This is why this issue exists, so we can improve this aspect of the package to make it even better...

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!

@PandaGeek1024
Copy link

PandaGeek1024 commented Mar 22, 2020

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

@mateusfccp
Copy link
Author

@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 bind and fmap (I think it's called only map in dartz). If you understand these concepts you will start to understand why composability and, thus, monads are a powerful tool. Of course this is only the surface, but it's a good start.

For starting, I think the most common (and simple) monads are Option<T> (also known as Maybe) (that is, conceptually, like a Either<(), T>), Either, Lens and IO (if you deal with... well... IO). You can look for these names on google, like "Either monad", "Maybe monad", etc, to get resources to learn these concepts.

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.

@mateusfccp
Copy link
Author

mateusfccp commented Mar 23, 2020

@spebbe

While making the documentation of Option, I noted that there was both bind and flatMap override.

What is the difference between bind and flatMap? Bot their signatures and implementations are exactly the same, and conceptually they are the same thing (usually, some languages call bind flatMap or vice-versa).

Why there are both functions in Option?

@spebbe
Copy link
Owner

spebbe commented Mar 23, 2020

@mateusfccp: they are both just aliases for monadic bind (aka >>=) for the exact reason you mentioned, namely that they are known by many different names. Also, I personally tend to use bind when dealing with monads that don't naturally map to collections in my head (like Free and Evaluation) and flatMap otherwise, but it depends on how your brain is wired I guess.

@spebbe
Copy link
Owner

spebbe commented Apr 26, 2020

Just released 0.9.0. All merged to master, so feel free to rebase!

@PandaGeek1024
Copy link

Is this a not documentation related release? I can't see that changes.

@pedromassango
Copy link

Follow this to learn the how this package works, this one is for Kotlin but the concepts are the same: #36 (comment)

@ibcoleman
Copy link

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.

    Future<Either<Exception, String>> getHeader() {
      return Future.value(right("header"));
    }

    Future<Either<Exception, String>> getBody(String header) {
      return Future.value(right("$header:body"));
    }

    Future<Either<Exception, String>> getFooter(String body) {
      return Future.value(right("$body:footer"));
    }

   // The closest I've gotten: but for some reason the second fold needs an explicit cast
    Future<Either<Exception,String>> fullValueFE = getHeader()
        .then((value) => value.fold(left, (headers) => getBody(headers)))
        .then((value) => (value as Either<Exception, String>).fold(left, (body) => getFooter(body)));

    fullValueFE.then((it) => it.fold(
            (err) => fail("Shouldn't Be Here"),
            (body) => expect(body, equals("header:body:footer"))));
     });

@spebbe
Copy link
Owner

spebbe commented Apr 30, 2020

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 traverseFutureM, which would make the .flatMap(id) unnecessary, but I haven't added that yet...

While monadic values do compose through bind/flatMap (which I think is what @mateusfccp
was referring to in an earlier post), monad "instances" don't compose in general. Sometimes they do, though! Evaluation is a pre-composed über-monad that, among other things, combine the effects of Either and Future. Here's an example of your code using Evaluation:

      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 Evaluation, I know... Maybe Dart will get proper typedefs/alias in the future -- then we could hide it :-)

You'll find lots of yummy methods on M, such as liftFuture, liftEither and raiseError for integrating with Future and Either.

Hope that helps!

@ibcoleman
Copy link

Thanks, that was very helpful!

@ibcoleman
Copy link

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 Future<Either<MyException, MyValue>>. I can understand the use an Evaluation in the case where you have an Either<MyException, MyValue> (just M.liftEither!), or in the case where you have a Future<MyValue> (just M.liftFuture!), but I'm really having a hard time understanding how you can make use of it in the case were you're dealing with a Future<Either<A, B>>. I'm guessing I would need to refactor those functions that return Future, to return Future and somehow use the EvaluationMonad to set the E value on the Monad?

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 spebbe mentioned this issue May 29, 2020
@mateusfccp
Copy link
Author

mateusfccp commented May 30, 2020

@spebbe
I'm currently documenting Functor... I know, obviously, what map is, as it is foundational to functor.

I never, however, had heard of stregthL/R. I made a quick search on nLab; is this function based on the tensorial strength concept? What would be a natural use-case?

@spebbe
Copy link
Owner

spebbe commented Jun 1, 2020

@mateusfccp
These are borrowed from scalaz (https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Functor.scala) and yes, I believe they are related to tensorial strength. cats calls them tupleLeft and tupleRight, which maybe are better names?

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 tupleLeft (cats) usage in monocle: https://github.com/optics-dev/Monocle/blob/master/core/shared/src/main/scala/monocle/function/FilterIndex.scala

@enzotar
Copy link

enzotar commented Sep 1, 2020

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

@Praful
Copy link

Praful commented Mar 7, 2021

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 Box is an identity functor (whatever that is!). Applicative and monad are later mentioned.

I'm still working my way through the video but I thought there must be a Dart library that has Box and other functional constructs defined and found the Dartz library.

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 Box in Dartz!

@modulovalue
Copy link
Contributor

Hello @Praful
You shouldn't try to translate javascript code 1:1 into a language with a nice type system like Dart.
Using the type system to your advantage will be much more valuable than any functional programming pattern on top of untyped code ever could.

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();
  }
}

@spebbe
Copy link
Owner

spebbe commented Mar 8, 2021

Hi @Praful!

As @modulovalue mentioned, nextCharForNumberStringImp was already very close to pure to begin with and didn't need identity functors or anything like that to make it functional.

That example aside, if you want to map the text you are following to dartz constructs, dartz has IdM, which serves as an identity monad (and therefore also identity functor). It doesn't reify the identity "box" though and therefore doesn't offer OO-style syntax. If you want to explore functorial/monadic style, you could use Option and just ignore it's partiality handling properties, like this:

  // 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 dartz:

ilist(['+', '-'])
  .ap(ilist(['a', 'b', 'c'])
    .ap(ilist([(x) => (y) => x + y])))

@apiep
Copy link

apiep commented Mar 8, 2021

I think what @Praful want to achive is making his own custom monad which is called Box. Not just "chainable" computation but rather putting a variable inside a "context" which can be computed inside those context.

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 Future, Stream or just a List for testing, but I find it quite difficult to implement such thing in dartz.

@Praful
Copy link

Praful commented Mar 10, 2021

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 int? is optional since Dart can see you checked for null before using the value. Clever!

@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 (nextCharForNumberStringFP) being less functional. In the next lesson, an extra function is introduced. Here's my version using dartz:

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

return String.fromCharCode(number+1).toLowerCase();

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 Box) possible with dartz?

@modulovalue
Copy link
Contributor

You probably know that the type declarations are optional because Dart works out the types...

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();
}

...if it doesn't, it will let you know :)

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!

@Praful
Copy link

Praful commented Mar 10, 2021

Yes exactly, but your Box translation to Dart is untyped.

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.

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!

I was not aware of that. Adding those options produced interesting results in some code I had :)

@mleonhard
Copy link

@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 Either class. The either_dart package is unmaintained but has some useful documentation. You could copy it. It's MIT-licensed:

Either<L, R> class Null safety

Represents a value of one of two possible types. Instances of Either are either an instance of Left or Right.

Left is used for "failure". Right is used for "success".

After reading that, I realized that Either is like Rust's Result. And that's what I was looking for in my code. I want a dart Result with an unwrap() method that throws the exception. I didn't find anything like that in dartz. If dartz has it, the Either docs could link to it in a 'Related' section.

@ibcoleman
Copy link

ibcoleman commented Apr 29, 2021

@mleonhard

I want a dart Result with an unwrap() method that throws the exception.

I could be wrong, but I think you want leftMap:

    final exceptionE = left(TvaMobileException('Uh Oh'));
    exceptionE.leftMap((l) => throw new YourException(l.cause));

I think the idiomatic way of (eventually) dealing with exceptions via Either would be using fold():

    someEither.fold((err) => { handleError(err); }, (ok) => { happyPath(ok); });

Sorry if I misunderstood your question.

@ibcoleman
Copy link

ibcoleman commented May 6, 2021

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!

@spebbe
Copy link
Owner

spebbe commented May 7, 2021

@ibcoleman Really glad you liked the video!

@mleonhard Thank you for your kind words and suggestions! The lack of unwrap() and get() operations on Either and Option is intentional, since they break referential transparency. The approaches described by @ibcoleman could be captured in simple extension methods in your own code or possibly in dartz_unsafe, but I would rather not put them in the main library since they tend to obscure the "FP way of doing things".

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

No branches or pull requests