Add examples to the Fun module - #12452
Conversation
|
A prototype of these examples was present in the |
| ]} | ||
| Note that given a function [(f : a -> b -> c -> d)]: | ||
| - [(flip f x : a -> c -> d)] and [(x : b)], whereas | ||
| - [(flip (f x) : c -> b -> d)] and [(x : a)] |
There was a problem hiding this comment.
Honestly, I am (as expected) unconvinced by those examples, and I fear that trying to find good examples for flip is a wild good chase. I would propose to split this case to another PR if you want to argue for it in more details.
There was a problem hiding this comment.
I looked up uses in OCaml code out there, and In most cases flip is used
- to match arg order with another function.. e.g. the
List.revexample from here.. which I could replace with aSet.addexample at the risk of more code just causing obscurity, - to change datalast to datafirst.. e.g. to filter a list by which elements being members of a set:
filter (flip S.mem s).. somewhat related to the first case, - or to change the arg order of a binary operator where the order has semantic meaning.. e.g. the
flip compareandsubtractexamples.
| - [(flip f x : a -> c -> d)] and [(x : b)], whereas | ||
| - [(flip (f x) : c -> b -> d)] and [(x : a)] | ||
|
|
||
| {!val:negate} |
There was a problem hiding this comment.
It might work better to have a thematic predicate section which could contain example of negate, const true and const false.
There was a problem hiding this comment.
There's value in having one overarching theme in the examples, I agree, but I feel it'll take a bit of power out of the other examples. WDYT? I'll try to come up with nicer examples to the other three following the predicate theme...
There was a problem hiding this comment.
Maybe we could start the negate section a short description of predicates as functions of type 'some -> bool often used in List.find_all and similar functions. Then Fun.negate could be presented as quick (aka without eta-expansion) way to reverse a predicate.
There was a problem hiding this comment.
Forgot to reply here, I added the passage:
{3:hnegate {{!val:negate}negate}}
Mainly used for reversing a predicate in a function which expects one, like
{!val:List.find_all} and similar functions
I opted not to explain the word "predicate", relying on the precedence in negate's own doc comment, and leaving space for the explanation to be put there instead.
| [(fun ... -> ...)] explicitly. | ||
|
|
||
| The examples below will be demonstrating this mainly with the {!module:List} | ||
| module. |
There was a problem hiding this comment.
I am wondering if Seq would not be a better illustration. In particular, we could have a section on building sequences to illustrate const and id and Seq has more sequence constructor like forever or ints to use as a basis of more examples.
There was a problem hiding this comment.
The problem with using Seq, answering also your comment on const above, is in three aspects I imagine:
- it's less straightforward due to its delay semantics, and its potentially infinite nature.. Instead of focusing on one function, there's a potential to add more distractions (
List.of_seq,Seq.take,Seq.equal...) - it's less common in code and pedagogical material. list manipulation is one of the first things beginners work through
- it doesn't play as nicely with the toplevel evaluation theme we have going on, because
Seq.tprints as<fun>
|
Changes done since the last review:
|
Thinking about adding this at the bottom. Is it a good idea? |
|
i'd love to see this merged, i think it's good |
|
@NoahTheDuke Thanks for reminding me to resume work on this. I was deliberating on whether or not to split into one PR for each function, but I feel like in doing so we might achieve a good example set in one of them that compromises the rest when the document is considered in full. I'm trying to nail the "overarching theme" as Octachron suggested. I want to avoid local maximums so I'm hoping to finish the discussion here. I'm working on something unrelated currently that's eating up most of my day. When I get a chance in a day or two from now, I should also update the PR to include examples for |
f2204bf to
3163ee2
Compare
|
It turned out to be a month not a day or two, apologies! The PR should preferably be reviewed as one document at this point, I should add here that, at this stage, I'm open to reviewers requesting whole examples and paragraphs removed outright if they deem them redundant or weakening etc. I've added a bit of variety and I'm satisfied with each example. I'm not too attached to any single example as long as the maintainers judge the set they choose to keep meets the desired level of understanding and showcase of utility. Notably, I've added cautions regarding the value restriction and regarding readability, to guide the reader against going too crazy with these functions. I'd really appreciate a second pair of eyes on the generated document, once the maintainers have approved/selected the content. Suggestions for better prose, grammatical mistakes, and the overall aesthetic with element choices are very welcome. I have written a good amount of documentation, but I'm no ocamldoc guru. There was a best-effort attempt to follow best practices nonetheless. |
|
|
|
(Actually, let me think through Interestingly for I personally don't use compose much if at all because it's not an operator, so multiple compositions don't improve readability, and writing out I did consider an example which shows a pipeline of Perhaps a fun way to use compose is for difference list appending, but I didn't want to introduce that whole concept. It's not a data-structures tutorial. Perhaps if one day in the far future difference lists are included in the stdlib, compose would make a cameo in its docs. 😁 Speaking of the compose examples, these are common use-cases of compose, but perhaps the combinator is useful in something more specific to us? It was a bit hard for me to dig through sherlocode comprehensively for this one, because it's often an operator, and doesn't have "standard" notation (the prior PRs acknowledge that fact). What I did dig out showed pretty much what I included here: selectors like |
Yes, of course! |
|
Few questions after stepping back and thinking a bit more about the document:
For me, 1. I'd like strong examples yes, 2. Seems reasonable to guide against future friction, 3. indifferent, 4. It's certainly more flexible, but it's the style used in the doc comments of each function itself. Examples should be the concrete aid to the abstract equation, 5. unsure but depends on 2 anyway, 6. against |
| let rec chain = function | ||
| | [] -> Fun.id | ||
| | f :: fs -> fun x -> f (chain fs x) | ||
| ]} |
There was a problem hiding this comment.
Another advanced example is as a final continuation for functions in continuation-passing-style:
type 'a btree =
| Leaf of 'a
| Node of 'a btree * 'a * 'a btree
let rec map_cps f tree k =
match tree with
| Leaf v -> k (Leaf (f v))
| Node (left, v, right) ->
map_cps f left @@ fun left' ->
let v' = f v in
map_cps f right @@ fun right' ->
Node (left', v', right')
let map f tree = map_cps f tree Fun.idThere was a problem hiding this comment.
That was originally in place of chain, but I wasn't sure if introducing the concept of CPS in an example was a case worth defending
| of two lists (with order and duplicates from the second) | ||
| {[ | ||
| # List.find_all (Fun.flip List.mem [2; 3; 5]) [0; 3; 3; 2; 4; 6; 8] | ||
| - : int list = [3; 3; 2] |
There was a problem hiding this comment.
I don't really like this example and the one below. They use the form flip f x to partially-apply the second argument of f, which I think is too clever, I would rather write an explicit function in this case.
There was a problem hiding this comment.
That was what I was going for, flip with the second argument partially applied. I don't mind removing it.
The rationale was that it was common enough (you see it in Set, Map, List, Stack, Queue, ... wherever you want to make datafirst become datalast or the opposite)
Update: it is removed
gasche
left a comment
There was a problem hiding this comment.
My overall impression of this category of examples is rather positive. This is a non-trivial, thoughtful contribution, I think it may be of interest to some readers; and it does an appreciated effort of warning about the sins of point-free programming. It may be a good discipline in the future when considering other combinators to ask for similar convincing examples and grow this documentation accordingly.
I propose to wait to see if other people want to give a second read and give another round of feedback. (Ping me in two weeks as I will forget to check.) Then I would propose to go ahead and merge.
|
So I couldn't leave this for a while and instead continued work on it haha I think I'm happy with the example set right now. I chose to shrink some sections to improve the focus in them. I'm ready for reviews and open to example additions (especially a second REPL example in compose... maybe...) The examples I removed, for reference: (* id *)
Scanf.scanf "%d" Fun.id
Float.Array.map_from_array Fun.id
if Sys.win32 then String.map (function '/' -> '\\' | c -> c) else Fun.id
(* const *)
let last xs = List.fold_left (Fun.const Option.some) None xs
(* flip *)
let subtract = Fun.flip (-) in
List.map (subtract 2) [4; 6; 8]
List.find_all (Fun.flip List.mem [2; 3; 5]) [0; 3; 3; 2; 4; 6; 8]
(* a flip reference of a present const example *)
let spellcheck known_words =
String.spellcheck ~max_dist:(Fun.const 2) (Fun.flip List.iter known_words)
(* compose *)
List.find_all (Fun.compose ((=) 3) String.length) ["one"; "two"; "three"]
let buf = Buffer.create 16 in
List.iter (Fun.compose (Buffer.add_utf_8_uchar buf) Uchar.of_int) [0x49; 0x2764; 0xfe0f; 0x1f42b];
Buffer.contents buf |
|
@hyphenrf I would like to move to merge this, but for this we need two things:
|
And make the example conform to the description
These are instead of the problematic examples
And warn about too many combinators
9575b93 to
a2516f9
Compare
|
Done (Also updated the branch because I had some local issues with the shallow repo I cloned and originally based this branch on, sorry for the noise). I'm struggling with rendering the docs in a modern format (the For reference, this is exactly what I did to generate the docs: |
|
Merged! We haven't had a final look by documentation-building experts, but I'm impatient to get the nice contribution in so I merged anyway. If there are minor rendering glitches we can always fix them later. |
|
Well, I'm thankful for the patience with the reviews 😁 |

In a similar spirit to #11476, I tried to add examples of varying complexity to the combinators in
Stdlib.Fun.The convention I chose to follow was the one agreed on in the aforementioned PR.
In addition to examples, I also noted what I think may be "gotchas", namely the evaluation of
const's argument and usingflipwith n > 2 args functions.Will add a changes entry if this is deemed a worthwhile change. Thanks in advance to kind reviewers!