fp-ts icon indicating copy to clipboard operation
fp-ts copied to clipboard

Making the documentation more welcoming to beginners

Open tstelzer opened this issue 4 years ago • 32 comments

📖 Documentation

I'm always excited when talking about fp-ts and its extensive ecosystem, but I always cringe visibly when asked about its documentation. I can see (and applaud) attempts to make it easier to get on board!

The "core concepts" page puts this nicely:

Don’t panic. If “higher order abstractions” and “type classes” sound like insanely complicated words to you, the first thing you should know is that you don’t have to understand all the intricate details of fp-ts in order to make good use of it. There is a learning curve, certainly, but don’t fear that you can’t learn to use fp-ts effectively, even as a beginner in functional programming. The trick is to start using the parts that are easier to understand and then gradually expand your knowledge.

Yes, that's the spirit!

But.

For a minute, assume the position of a bright-eyed programmer, who's just heard about functional programming and is excited to have found this extensive toolkit in typescript; fp-ts. Let's say they are self-taught. They started to get into this field, learning object-oriented programming. Or maybe they started building games, when they were young and are now getting into web development via JS. Or maybe they studied CS a couple of years ago, just getting by, without ever really grokking the fundamental concepts. Point being, they are a workman, not an academic. I would call myself a workman programmer.

Now they jump into the documentation, eager to learn about this thing. So they click on "getting started", and are blasted away by theory. A couple of examples from the very first pages in "getting started" and "functional design":

In this article the term “combinator” refers to [...] A style of organizing libraries centered around the idea of combining things. Usual So I know that, ly there is some type T, some “primitive” values of type T, and some “combinators” which can combine values of type T in various ways to build up more complex values of type T

and

The declaration may be read as a type A belongs to type class Eq if there is a function named equal of the appropriate type, defined on it What about the instances? A programmer can make any type A a member of a given type class C by using an instance declaration that defines implementations of all of C’s members for the particular type A.

or this last sentence on "getting started":

Finally another useful way to build an Eq instance is the contramap combinator: given an instance of Eq for A and a function from B to A, we can derive an instance of Eq for B

Most of the documentation strikes me as overly eager to jump into the theory, using language that prevents access to a large chunk of people. It is most certainly not simple language. It assumes a lot of prerequisite knowledge. It is probably a case of the curse of knowledge. I think these articles are great for advanced students of functional programming. Or for computer science graduates. When I started to learn about FP, I was neither. So, I believe we can start simpler. If we want to make functional programming mainstream. I most certainly want to!

But I don't want to complain, let's fix it! I started to write an introductory tutorial series for functional programming with fp-ts. The series is inspired by a series of workshops I am doing (and further planning to do at work), introducing working programmers to the paradigm. The initial, achievable goal is to get up to function composition. Without going into Type classes at all. HOF, partial application, currying, compose (well, pipe and flow).

~~You can see the progress over at [redacted].~~ I'll create and link a dedicated repository!

In addition, I'd propose to restructure and simplify the hierarchy a bit:

  • Introduction kind of hides a couple of tutorials under "Learning Resources". Hard to find and inconsistent, imho.
  • Getting started is already too advanced. I'd propose to add an introductory section (well, what I am currently writing) before this stuff.
  • Functional design also starts very high-level. And it feels a bit out of place. Not sure what to do with it.
  • Recipes is awesome! Some of those articles I'd expect to see in Getting started, for example "Async tasks". Again, not sure what to do with it. Maybe merge with Getting started?

I guess in summary what I'm getting at is:

  1. We need to be more welcoming to beginners, and not overwhelm them with complexity.
  2. We really need a clear narrative through our documentation. Having used fp-ts quite a bit now, I still don't know where to start.

Would love to hear your guys input on this.

Best regards, Timm

tstelzer avatar Jan 24 '20 20:01 tstelzer

Adding (and allowing) non strictly technical material was a mistake, my mistake, I'm sorry.

Non technical documentation is always opinionated, I don't want to get involved in editorial work nor push a particular way of learning functional programming (mine included).

The solution is to remove all non technical documentation and to reserve a couple of pages for links to external resources (namely the already existing "Learning Resources" and "Ecosystem").

There's plenty of possibilities to host non technical documentation elsewhere, maybe a community driven handbook? @tstelzer your introductory tutorial series and the resources in "Recipes" could be the seed.

This PR realizes the following structure:

  • Learning Resources (<= links to external tutorials + links to internal advanced code examples)
  • Ecosystem (<= links to libraries)
  • Modules (<= API reference)
  • Guides (<= technical miscellanea)
    • Write type class instances
    • Migrate from PureScript/Haskell
    • Upgrade to fp-ts 2.x

From now on I will only consider PRs containing:

  • examples of API usage (through the @example tag in the source code)
  • links to external repos, tutorials, blog posts, code examples, etc... (file docs/learning-resources.md)
  • links to libraries based on fp-ts (file docs/ecosystem.md)

gcanti avatar Jan 26 '20 06:01 gcanti

While I helped create some of the resources that are now going to be removed, I agree with @gcanti that adding focus by removing more general docs could be a good way forward for this repo.

The reason for this, from my point of view, is that there is too broad an audience for a single way to document fp-ts, because depending on who you ask fp-ts can be many different things to the people using it. Having observed issues in this repo over the last year showed to me that there are quite different user groups that would have to be addressed, which is very difficult to do. Not that it could not be done, but it would be a project in its own rights – which is basically the suggested way forward here, as far as I understand it.

From the top of my head, I see as audiences:

  • people interested in advanced Category Theory
  • people interested in taking first steps in Category Theory
  • people not interested in the maths at all
  • people interested in the maths but not the jargon
  • people exploring advanced functional programming concepts to stretch TypeScript's boundaries
  • people who just want a solid library for TypeScript
  • people from other functional languages who are looking for a library to help them continue using their preferred style and idioms
  • people from JavaScript who are looking for ways to work in more strongly typed ways
  • people who just need to get a job done and understand that fp-ts might help them
  • people whose colleagues have started using fp-ts and now they are forced to use it
  • people who know exactly what they want from this library
  • people who are interested to learn new things they can do with this library
  • … and many more!

This list is not intended to judge anyone – everyone is welcome to use fp-ts in ways that work best for them! But I think this is a source of the difficulty of writing documentation for fp-ts: to some jargon will be very helpful while others find it discouraging. Every group needs something different (but they can still all be friends ❤️) .

So, while in some ways a suprising move, I think removing editorial content from this repo could be a win – there are still plenty of API docs to be written, from which everyone can benefit.

It would be great if it could find a new home – or rather new homes. If you would like to work on this @tstelzer I would be happy to help out a bit. My recommendation would be to have a clear focus and a clear target audience to not get entangled in trying to do too many things: “fp-ts for people new to TypeScript” or “fp-ts by example” or “Advanced fp-ts concepts” or “Learn Category Theory using fp-ts” etc. are all very interesting topics but probably for different audiences.

As inspiration, these are types of documentation that I could totally see to be interesting for fp-ts and that I could think of right now (there are certainly more). By being separate from the main library, the library can focus on providing the best abstractions, while the docs can help different audiences learn it.

  • The Mostly Adequate Guide is a great introduction to monadic functional programming for beginners that is not about any specific library at all (but very relevant to fp-ts, for example)
  • How to learn D3.js is a very visual way to understand the d3 ecosystem and get started
  • Learn RxJS is a companion documentation to RxJS that is focused on getting you to a comfortable level
  • The RxJS operator decision tree is a step-by-step questionnaire that will guide you to the operator that would be useful to your use-case.

grossbart avatar Jan 26 '20 15:01 grossbart

@gcanti Hey, thank you for responding! I want to make it very clear that I love your work and deeply appreciate that you are effectively driving the typescript functional programming community with the fp-ts ecosystem. You don't need to apologize for anything! You don't owe anyone anything, not me, not "beginners". I think its immensely important to highlight this fact!

But, just to add my 0.02$: I understand the decision to redact non-technical documentation.

Though, I'd argue that fp-ts is the de-facto place to learn about, and using functional programming in typescript, so having introductory material driven by the fp-ts community, that goes beyond technical material, is reasonable. It isn't just a library. Its almost like another layer on top of typescript. In my opinion this warrants tutorials and the like that are married to the repository. If we treat them like documentation, they are more likely to stay up to date.

If we treat them like blog posts (e.g. medium):

  1. They tend to be forgotten and rot.
  2. There is no clear narrative through them.
  3. Its harder to clearly identify the target audience for each article (what @grossbart highlighted).

Regardless, I'm happy with outsourcing and then linking tutorials in the "learning resources" section, this is reasonable.

tstelzer avatar Jan 26 '20 15:01 tstelzer

I started a dedicated repository for the purposes of writing the introductory tutorial: https://tstelzer.github.io/workers-guide-to-fp-in-ts/.

tstelzer avatar Jan 28 '20 23:01 tstelzer

This is that I need! thank you @tstelzer for taking us into account.

nachocodexx avatar Jan 29 '20 01:01 nachocodexx

Because the recipes also needed a new home, I migrated them to their own repository just now so they are available again.

Mind you, there are still broken links in there and the examples need highlighting (or, preferrably, a REPL). But it's a start :)

https://grossbart.github.io/fp-ts-recipes/

grossbart avatar Feb 04 '20 18:02 grossbart

@grossbart your examples are very practical. If we can have examples for the modules like Array, function or how we can wrap inpure modules like moment in fp-ts that would be a blast!

aminpaks avatar Mar 10 '20 09:03 aminpaks

I'd love to see haskell style type definitions along with the typescript ones

Brettm12345 avatar Mar 10 '20 15:03 Brettm12345

@grossbart BTW I think the way you kick off the tutorial with Async and simply mapping Task and TaskEither is really nice. Makes me think very little about writing the whole program in FP in the beginning and at the end of that section it would be really beneficial if we mix them with some other things like mapping a TaskEither value to Task (with fold maybe?) and apply the value to some other Async operations.

I'm very new to FP and specially fp-ts, personally I find types in fp-ts hard to follow. That said I would definitely help once I learn a bit more. I'm writing a program fully in fp-ts right now. One of the concepts that I'm still struggling is Applicatives, having a Functor that its value is a function and apply the arguments with coming values from previous mapping in a pipe chain or something similar.

I would like to see some examples of those too.

aminpaks avatar Mar 11 '20 10:03 aminpaks

I'm definitely becoming more versed in this functional programming paradigm, but still a beginner.

I'd like to share my thoughts thus far for trying to learn this style.

I've engaged myself with resources online and have decided for me that I'm missing the following content:

  • Transforming imperative patterns into functional ones.
    • if, else if and else patterns.
  • Abstracted documentation (maybe filterable in a search)
    • Modules
    • A dependency graph for modules.
      • When looking at the module Apply, which other modules use it? Currently I would have to go to every other module and figure it out.
    • Filterable search for modules
      • Combinators
      • binds
      • maps
      • returns
    • What structure are the modules in? Example for import { option } from 'fp-ts';
      • option is the module.
      • option.option is the raw thing (functor?)
      • option.[x] can be types, combinators or other methods useful for handling the Option type.

As you can see, I still have no idea about some things but am trying.

waynevanson avatar Mar 30 '20 07:03 waynevanson

I actually like small samples from another helper library, https://github.com/JSMonk/sweet-monads/blob/master/maybe/README.md Another and more famous is the documentation for the Typescript, which start with basic staff like how to declare, how to use with special part grouped under Advanced tag Is it possible to write docs similar to this way?

Lonli-Lokli avatar Apr 05 '20 08:04 Lonli-Lokli

@Lonli-Lokli, @waynevanson

I understand how you feel exactly. I've been trying to wrap my head around FP for a long time. I think it's true that we learn quicker from smaller examples but at the same time it would be such a blast if we could rewrite all the exercises in Mostly adequate guide to Functional Programming with fp-ts.

Despite the fact I find the book an amazing source for beginners because of lack of some concepts like curry and the syntactic differences it doesn't help to quickly learn fp-ts.

I recommend you to read that book anyways as it is truly a great start.

aminpaks avatar Apr 05 '20 12:04 aminpaks

I think we all want to see lib thrive, and for that to facilitate its adoption, it is essential. I understand that for programmers used to Haskell and Scala, they are just looking for a way to transfer the features of those languages to TS, and a summary of what fp-ts exposes may be enough.

But several developers want to start adopting functional programming and have found fp-ts a gateway because it guarantees interoperability and allows for gradual adoption. I can have a file of my project, which uses functional programming concepts, using fp-ts. And this can spark interest, demonstrate the benefits that the paradigm brings and help to have more people using it.

I am not proposing a functional programming course embedded in the lib, as documentation. But that each module is presented with a brief explanation of what it does and how to use it would help a lot.

Maybe we can use a codesandbox, for that.

We have already suffered from a lot of jargon, technical explanations. I believe it would be interesting to try to popularize the concepts and help more developers to adopt them.

The JavaScript / TypeScript community has always been very beginner's friendly. I think here we could have the same motto.

rafbcampos avatar Apr 13 '20 14:04 rafbcampos

Adding my two cent to this issue.

I recently ventured into trying to FP-ify my code more and got into concepts that were very alien to me (I tried to be functional with vanilla JS/TS, but discovered Functors, Monads and all the things thanks to fp-ts' link to the excellent Mostly' adequate guide.

I fell in love instantly, but was completely lost. Wrapping my head around all those new FP concepts was one thing, but finding which part of fp-ts I should be using was an insurmountable task for me and I eventually gave up, and the lack of example was making it hard for me to be sure I was choosing the right thing.

The documentation of Crocks ("A collection of well known Algebraic Data Types for your utter enjoyment.") is exemplary, here is an example of the documentation for Async.

A similar documentation with examples is crucial I think, but I realize that it's an enormous task.

pyrho avatar Apr 13 '20 23:04 pyrho

For the start with fp-ts, it is good to know Option, Either, TaskEither, and pipe. Docs are sparse but good enough. Do not try to understand the theory behind it. Focus on examples. It's the same with CSS, you do not read the CSS specification either.

Or maybe, should I read the CSS specification to finally understand how to center div?

steida avatar Apr 13 '20 23:04 steida

Sorry, I should articulate better what I meant. It's not about knowing Algebraic Data Types, or learn how to hack Higher-Kinded Types into TS using object literals, what is pretty smart, btw.

It is about having an explanation that makes clear that all the API is about curried functions, and that you need to import helper functions to achieve what, for a JS/TS programmer first intuition, should be a method available in the object. So instead of some.map().chain().fold(), you need to pipe that, with helper methods, pipe(some, map(), chain(), fold()).

It's about been more beginner-friendly, not an FP beginner, but a user starting to adopting the lib.

Don't get me wrong. I believe anyone here sees the excellent work done. We only want to add some documentation (not so thirsty as type definitions) to help it become even better, with more people using it.

Unfortunately, who are more interested in better docs are those who are having difficulties in understanding how to use it, and, therefore, can't contribute to that.

rafbcampos avatar Apr 14 '20 02:04 rafbcampos

@gcanti Is there anyway we can help?

  1. Do you think the documentation should be part of this project?
  2. Do you have some ideas how to discuss more complex examples apart from your articles (yours are great but too simple)?

aminpaks avatar Apr 14 '20 11:04 aminpaks

@steida

Do not try to understand the theory behind it.

I'd say this is a debatable and, maybe, even a bit dangerous idea to spread.

enricopolanski avatar Apr 14 '20 12:04 enricopolanski

@enricopolanski I should have chosen better words. I meant, for that start. The biggest problem when teaching FP is to explain WHY. People need to see a code first. Before and after. Of course, you are right.

steida avatar Apr 14 '20 18:04 steida

I read the book and the docs but I'm struggle on how to get an IO value from local storage, parse this as JSON, and return the object 😅. Definitely, I'm one of the people would love to see more beginner samples using fp-ts, even a simple Todo List saving the data in local storage and handling possible failure scenarios like invalid JSON, non existent storage, etc.

BrunoQuaresma avatar May 30 '20 17:05 BrunoQuaresma

@BrunoQuaresma I'm more and more convinced that a series of sample code snippet from Mostly' adequate guide would make a big difference.

I'm willing to do it, I just need to get into it and figure out where to start.

aminpaks avatar May 30 '20 18:05 aminpaks

@BrunoQuaresma Hi :-)

It's easier than you think. It's also one of the first links in https://gcanti.github.io/fp-ts/learning-resources/ This one https://dev.to/gcanti/interoperability-with-non-functional-code-using-fp-ts-432e

For start, you really don't need to know more than IO (sync always success), Either (sync success or error), Task (async success), TaskEither (async success or error), and pipe.

And you don't have to use IO if you don't know why you should. Start with replacing throw/catch with Either (sync) TaskEither (async).

loadFromLocalStorage can return Option, if you don't care about why it failed. If you care, then it should return Either. These primitives are described enough in docs I suppose.

IO allows you to defer execution and compose IOs with sequenceT (tuple), sequenceS (struct).

It's almost all the time the same pipe(foo(), either.chain or taskEither.chain or map or mapLeft etc.)...

To handle Option, Either, etc. use fold. It forces you to handle both/all cases.

Feel free to ask me on Fauna Slack.

And check this talk to get the bigger picture https://www.youtube.com/watch?v=Nrp_LZ-XGsY

steida avatar May 30 '20 20:05 steida

@steida thanks for share the references. I already read/watch some of these.

I tried to make this working during the day without success and I was not able to make load local storage and parse JSON working together. The maximum that I got was this:

pipe(
  IOEither.tryCatch(
    () => Option.fromNullable(window.localStorage.getItem("item")),
    Either.toError
  ),
  IOEither.tryCatch(JSON.parse, Either.toError)
);

But, JSON.parse is receiving Option<String>, I tried to use map, chain, fold without success 😢. I asked some help from a friend who works with Scala and he was a bit confused about the fp-ts terminology.

BrunoQuaresma avatar May 30 '20 22:05 BrunoQuaresma

@BrunoQuaresma

import { either } from 'fp-ts';
import { Either } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';

// if (typeof window !== 'undefined')
//   window.localStorage.setItem('item', JSON.stringify({ foo: 1 }));

// // That's how we should start.
// // Define function input and output.
// // Note errors as some union/sum type and unknown as success.
// // Use `throw ''` to silence TypeScript type errors.
// const getJsonFromLocalStorage = (
//   key: string,
// ): Either<'notFound' | 'jsonParseError', unknown> =>
//   pipe(window.localStorage.getItem('item'), (a) => {
//     throw '';
//   });

export const getJsonFromLocalStorage = (
  key: string,
): Either<'notFound' | 'jsonParseError', unknown> =>
  pipe(
    window.localStorage.getItem(key),
    // When you don't know, put `a => a` and mouse hover over it.
    (a) => a,
    // Create Either from nullable with 'notFound' as Either left.
    either.fromNullable('notFound' as const), // Note as const. Try to remove it.
    // Mouse hover over `(a) => a` to see what we have.
    // (parameter) a: either.Either<"notFound", string>
    (a) => a,
    // ChainW is chain with type widening. Try to remove 'W'.
    either.chainW((a) =>
      either.tryCatch(
        () => JSON.parse(a) as unknown,
        () => 'jsonParseError' as const, // Note as const. Try to remove it.
      ),
    ),
  );

In the real app, each (almost) step in pipe would be a separate function, so you would not need as const because you would have to describe function return type explicitly and TypeScript would handle it correctly. The same (almost) for *W functions.

Usage. Note Either left should be handled with typescript exhaustive checking. fp-ts has a helper for that, absurd function.

pipe(getJsonFromLocalStorage('foo'), either.fold(..., ...) or map, chain, etc.)

steida avatar May 31 '20 00:05 steida

Thank you @steida !

BrunoQuaresma avatar May 31 '20 12:05 BrunoQuaresma

Is there any chat\gitter where some help\advise might be requested?

Im new to the FP style, especially in Javascript so my question is in usage of io-ts with fp-ts, eg I have a IO task (ie loading array from endpoint), parser defined with io-ts and consumer aka my UI component. What is the best type I should get into my consumer - Either<ApiError, Either<ValidationError, ModelItem>[]> ?

Lonli-Lokli avatar Jul 13 '20 21:07 Lonli-Lokli

@Lonli-Lokli there's https://fpchat-invite.herokuapp.com/ one of the channels is #typescript (another is #fp-ts)

gcanti avatar Jul 14 '20 05:07 gcanti

I want to say that I've been seeing heaps of work put into documentation.

For example, the @category in ts-doc tags and therefore in the docs is really helpful on getting a grasp on it all.

Great work to anyone involved! :)

waynevanson avatar Jul 15 '20 09:07 waynevanson

As someone who is trying to learn FP with the goal of helping out on existing project which uses fp-ts, I wanted to put in my two cents on the experience so far...

My primary learning resource has been the "Mostly Adequate" book, which I've found very good. However, the big challenge has been trying to translate the code examples in that book to fp-ts.

IMO, it would be EXTREMELY helpful if someone took the time to translate some of the examples and put together a brief overview on the key differences. Almost like a companion resource for "Mostly Adequate" that discusses how the concepts translate to this library.

I'd be embarrassed to tell you how long it took me to translate the this code to fp-ts equivalent, and I'm sure I'm still probably not doing it the ideal way:

// taken from https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch08

// withdraw :: Number -> Account -> Maybe(Account)
const withdraw = curry((amount, { balance }) =>
  Maybe.of(balance >= amount ? { balance: balance - amount } : null));

// This function is hypothetical, not implemented here... nor anywhere else.
// updateLedger :: Account -> Account 
const updateLedger = account => account;

// remainingBalance :: Account -> String
const remainingBalance = ({ balance }) => `Your balance is $${balance}`;

// finishTransaction :: Account -> String
const finishTransaction = compose(remainingBalance, updateLedger);

// getTwenty :: Account -> Maybe(String)
const getTwenty = compose(map(finishTransaction), withdraw(20));

getTwenty({ balance: 200.00 }); 
// Just('Your balance is $180')

getTwenty({ balance: 10.00 });
// Nothing
// translated to fp-ts 
import { flow } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";

type BankAccount = { balance: number };

// withdraw :: Number -> Account -> Maybe(Account)
const withdraw = (amount: number) => 
  ({ balance }: BankAccount): O.Option<BankAccount> => O.fromNullable(balance >= amount ? { balance: balance - amount } : null);

// updateLedger :: Account -> Account 
const updateLedger = (account: BankAccount) => account;

// remainingBalance :: Account -> String
const remainingBalance = ({ balance }: BankAccount) => `Your balance is $${balance}`;

// finishTransaction :: Account -> String
const finishTransaction = flow(updateLedger, remainingBalance);

// getTwenty :: Account -> Maybe(String)
const getTwenty = flow(withdraw(20),  O.map(finishTransaction));

console.log(getTwenty({ balance: 200.00 })); 
// Just('Your balance is $180')

console.log(getTwenty({ balance: 10.00 }));
// Nothing

I've found it pretty challenging to navigate the import structure and since the concepts don't line up 1-to-1, a lot of the time I don't even know what I'm looking for. Took me forever to figure out there is no compose but there is flow and it's kinda the same thing, just left -> right (I found pipe first, which led to even more confusion).

The examples provided in the documentation have been somewhat helpful in getting here, but overall I find most of them to be incomplete. Like setting up an optional, but never actually going as far to "fold" it out.

ChuckJonas avatar Mar 24 '21 19:03 ChuckJonas

Since I found myself already having done a lot of this through my learning (although likely incorrect), I went ahead and started such a resource.

https://app.gitbook.com/@cjonas/s/mostly-adequate-fp-ts/

https://github.com/ChuckJonas/mostly-adequate-fp-ts

If anyone is interested in helping out, it would be appreciated as I don't really know what I'm doing 😉

ChuckJonas avatar Mar 25 '21 17:03 ChuckJonas