Open davidchambers opened 8 years ago
After reading the summary/introduction, the full picture of where Sanctuary extends beyond Ramda is not entirely clear. Does it add new functions or just replace existing Ramda functions with type safe versions?
Earlier today @dypsilon described Sanctuary as "a Ramda reboot". I had not thought of it this way, but it's an accurate description.
The two libraries have a great deal in common philosophically. Points of agreement:
The two libraries differ philosophically in a few significant ways. Points of disagreement:
Let's consider the points of difference. Ramda provides some variadic functions (such as R.pipe
); Sanctuary provides none. Many of Ramda's higher-order functions accept variadic functions. In my view—a view shared by @Avaq and @benperez—this increases implementation complexity, documentation complexity, and usage complexity. I rarely succeed in getting R.lift
to behave as I expect, yet S.lift2
is one of my favourite functions. I can look at its type signature and see how to use it:
S.lift2 :: Apply f => (a -> b -> c) -> f a -> f b -> f c
With R.lift
on the other hand, I'm forced to read its description as I don't know what to make of this:
R.lift :: (*... -> *) -> ([*]... -> [*])
Ramda has many partial functions, such as R.head
. Sanctuary is able to provide total equivalents as it has access to Maybe and Either which encapsulate the notion of success and failure. Not only do these types allow Sanctuary to provide safe versions of unsafe Ramda functions, but these types are available for one to use in one's own code.
Ramda and Sanctuary disagree on whether add('foo', 'bar')
should evaluate to 'foobar'
or raise an exception. Ramda's approach could be described as "let it fail (or succeed with an erroneous value)", whereas Sanctuary goes to great lengths to prevent type errors from going undetected.
On this last point, @buzzdecafe has said that he believes run-time type checking would benefit Ramda. It's possible Ramda and Sanctuary will reconcile this difference. :)
I was primarily talking about Ramda and Sanctuary in the context of working with types. I think Ramda does a great job of working with native data types. You could just drop Ramda in any project and have a great time working with those arrays and strings in a somewhat functional way.
On the other hand, Ramda is awkward for working with algebraic types as defined in Fantasy Land. This is the reason why there are so many libraries which redefine simple functions, already included in Ramda, for working with algebraic types.
My suggestion was to stop complicating Ramda with algebraic type classes and leave it to Sanctuary. Especially because the former doesn't have any type checking on board.
@davidchambers i am coming around. my major problems with run-time type-checking are
But the benefits are clear, and I don't dispute that something is better than nothing.
Ramda is awkward for working with algebraic types as defined in Fantasy Land.
I don't see it this way. I quite happily use R.map
and R.chain
with algebraic data types provided by Fluture, Folktale, and Sanctuary. I appreciate Ramda providing these functions irrespective of whether Ramda provides any algebraic data types.
My suggestion was to stop complicating Ramda with algebraic type classes and leave it to Sanctuary.
I consider transducers the most significant source of complexity in Ramda's implementation. Having R.map
, R.chain
, etc. dispatch doesn't strike me as problematic, though providing usage examples for certain functions—R.sequence
comes to mind—is challenging if one restricts oneself to built-in types.
i see the placeholder as the biggest offender. I agree that transducers add complexity, but they also pay off. The placeholder adds overhead to the most fundamental operation in the libarry and delivers nothing that you can't get with flip
.
I agree that transducers add complexity, but they also pay off.
I agree. Cost–benefit analysis is important. I believe the benefit of R.map
, R.chain
, etc. dispatching significantly outweighs the cost of doing so.
I believe the benefit of
R.map
,R.chain
, etc. dispatching significantly outweighs the cost of doing so.
Yes, me too. The intent was to provide a point-free interface for FL types and look more ML-ish. I'm curious exactly what @dypsilon finds awkward?
Perhaps it's time for Ramda to consider how to pull Transducers into a side package such as Fantasy and Lenses.
The placeholder adds overhead to the most fundamental operation in the library and delivers nothing that you can't get with
flip
.
I'm not sure I go as far as that. There all sorts of things that could be done with, say, reduceBy
and the placeholder that cannot be done with flip
. Nonetheless, I would love to find a clean way to get rid of the placeholder and keep those abilities. I no longer find it very convincing.
Here is a very simple comparison between Ramda/Sanctuary function names. It is at least helpful for me to visualize some of the differences b/w the two libraries, so thought I would share: demo: http://rawgit.com/thurt/ramda-sanctuary-compare/master/index.html repo: https://github.com/thurt/ramda-sanctuary-compare
That's a useful document, @thurt! Keep in mind that the shared functions are in same cases equivalent when used correctly (e.g. trim
) but are in other cases significantly different (e.g. head
). Regardless, this document provides a good indication of the overlap between the two libraries.
True--comparing just by function name is like comparing apples-to-oranges in some cases. It looks like the either
function is one example.
http://ramdajs.com/0.21.0/docs/#either
https://github.com/sanctuary-js/sanctuary#either--a-c---b-c---eitherab---c
I would appreciate an example definition of a custom type class.
Also #390 looks like a duplicate (or at least related).
I would appreciate an example definition of a custom type class.
Are the definitions of Foo :: TypeClass
and Bar :: TypeClass
in the Z.TypeClass
documentation helpful? If not, how do you suggest we improve the examples?
@davidchambers This is certainly helpful.
In my opinion a perfect example would include:
TypeRep
definitionTypeClass
definitionThat's a good outline, @futpib. Would you like to take the lead on this? I'm happy to provide feedback.
If you're interested, you could open a pull request to this repository containing a single Markdown file, we could get feedback from the community and make changes, then copy the final version and paste it into the wiki.
Hey, are there any news to this ? Has an getting started been written yet? Would really love some documentation!
There's no update yet, @Fl4m3Ph03n1x. Sorry.
I ran a two-hour workshop at LambdaConf last month. It was recorded, and the recording will at some point be uploaded to YouTube. I'll share a link here once that has happened.
Would really love some documentation!
What would you like to know? Why one might use Sanctuary, or how to install and configure it, or how to use it, or some combination of these, or something else entirely?
Ah yes, the workshop would be great! As a beginner into Sanctuary and after reading some docs, I am aware of the following ( what I consider strong points of the documentations ):
Now, I feel I am missing the following:
Identity['@@type'] = 'my-package/Identity@1';
. Now I am sure this makes sense to you, but for me, I wonder why I need @@ for example. IMHO, code sample of small projects ( like reading from a file using Node.js fs
or makign an HTTP request ) would be really useful. I see half the API as having a ton of methods, but for me, I really don't see how most them when could be applied in the real world ( where and why would I use pipeK
instead of pipe
? ).
To this end, small demo projects ( which are usually presented at conferences ) are a great help, at least for me, and I really wish there were more.
As a side way, it would also be interesting to add a section of "relevant articles" that others made to help ease people into Sanctuary and to provide them with additional documentation.
Thank you for your thoughtful response, @Fl4m3Ph03n1x. I find it very helpful indeed.
As a case I mention the Configure section
Identity['@@type'] = 'my-package/Identity@1';
. Now I am sure this makes sense to you, but for me, I wonder why I need @@ for example.
@@type
provides compatibility with sanctuary-type-identifiers. Could we update the documentation to make this apparent?
It is totally not clear to me, reading from the docs, how I would integrate it within a project and define functions with signatures and so on.
I sympathize. Sanctuary has many moving parts and it's not obvious how one should fit them together.
Code sample to justify the long Maybe / Either APIs.
I'm not sure what you mean by this. Do you mean that Sanctuary contains many functions for working with values of types Maybe a
and Either a b
, and that it's not obvious why one would use some of these functions? If so, are there functions which seem particularly unhelpful?
As a side way, it would also be interesting to add a section of "relevant articles" that others made to help ease people into Sanctuary and to provide them with additional documentation.
This is another excellent suggestion. :)
I'm not sure what you mean by this. Do you mean that Sanctuary contains many functions for working with values of types Maybe a and Either a b, and that it's not obvious why one would use some of these functions? If so, are there functions which seem particularly unhelpful?
What I mean by this is that Maybe
has 18 methods, while Either
has 16. Now, while I agree that isJust
( for example ) is self-explanatory, others like encase
are not so ( why would I need it? eval
is kinda evil :p ).
Compared to, for example, Folktale, which has about 2 methods for their Maybe and Either ( Result ) types (getOr
and merge
) I personally find the API surface sanctuary offers quite staggering.
It is not to say that this is a bad thing, it's just that as a newcomer, I would be more inclined towards Folktale because I feel the learning curve would be smaller while retaining the same benefits Sanctuary offers.
This may be a clearer example for S.encase
:
const { unsafeGetUser } = require ('some-external-dependency');
// getUser :: Integer -> Maybe User
const getUser = S.encase (unsafeGetUser);
We've defined a pure function in terms of an impure function. :)
Your comments about the daunting API are well taken. The remedy, I believe, is not to remove functions but to provide tutorials so that new users are not forced to read what is essentially reference material.
@davidchambers I would argue that rights
and lefts
could be combined into
separate :: (Filterable f, Functor f) => f (Either a b) -> Pair (f a) (f b)
or
separate :: (Filterable f, Functor f) => f (Either a b) -> { lefts :: f a, rights :: f b }
Please open an issue for that proposal, Gabe. :)
Is there a way I could help to contribute to this guide? Is there something in the works that perhaps requires feedback?
Is there a way I could help to contribute to this guide?
That would be wonderful! Would you like to contribute to the wiki? If so I will create a new wiki entry. To encourage participation from me and others, I suggest publishing something and requesting input. :)
As suggested by @riston on Gitter.