CrowdHailer / fn.js

A JavaScript library built to encourage a functional programming style & strategy. - http://eliperelman.com/fn.js
MIT License
399 stars 30 forks source link

Version 1.0 mission statement, increasing focus #27

Open CrowdHailer opened 8 years ago

CrowdHailer commented 8 years ago

Proposal

Currently

A JavaScript library built to encourage a functional programming style & strategy

Proposed change

A library for manipulating first class functions in JavaScript.

Rational

There are several pieces that need to come together to encourage functional programming. These include some of the following.

  1. Map, filter & reduce on collections.
  2. Immutable data structures.
  3. controlled side effects.
  4. working with first class functions.

fn.js Does not try to tackle points 2 or 3 and nor should it. At the moment it tries to solve some of point 1 and all of point 4. fn.js does not tackle point 1 as well as alternatives, it is missing functionality like head, fist, last, tail, union, zip etc.

This proposal is to have fn.js NOT tackle point 1 and focus solely providing utilities for manipulating functions(i.e. point 4).

Repercussions

fn.somFunc(param, func) // => newFunc
// e.g. 
fn.throttle(100, myFunc) // => newFunc

fn.otherFunc([collection, of, function])  // => newFunc
// e.g.
fn.compose([myFunc, myOtherFunc]) // => newFunc
CrowdHailer commented 8 years ago

As well as depreciating functionality explicitly for handling connections there is functionality for handling functions that might be missing. e.g. times(n, fn)

fn.times(3, function(){ console.log('hello'); })
// => 'hello'
// => 'hello'
// => 'hello'
CrowdHailer commented 8 years ago

Change log for pending changes effecting 0.9 release. https://github.com/CrowdHailer/fn.js/blob/v0.9/CHANGELOG.md

eliperelman commented 8 years ago

Why drop methods that exist in ES5 because they have the same functionality? The point of having a different implementation is to introduce composability of methods, which cannot elegantly be done without state with native methods. I think this move would also kill pointfree style.

CrowdHailer commented 8 years ago

So the main reason I have for dropping them is to separate fn.js from being a general utilities library. With the possible merger of underscore and lodash they are fulfilling that role. With map/reduce/filter gone fn.js is only a function utilities library. For fn.js to work as a source of collection utilities it needs several more such as first etc.

eliperelman commented 8 years ago

The problem with underscore/lodash is that it's own core methods are not composable, notably because they don't follow the function-first/data-last paradigm.

CrossEye commented 8 years ago

I don't understand this proposal at all.

Functional programming is many things, but there are some common themes, and you've certainly hit on a few of them with your list. Immutability and referential transparency would be high on any list, and while a JS library is not going to enforce either idea, it can promote them, and can at least ensure that it doesn't mutate user objects or provide referentially opaque functions. It can avoid side effects, and can generally avoid providing functions whose main purpose have to do with side-effects.

Your other two points, 1 and 4, are a little different. #1, a collection of functions on a data structure is not simply nice to have; it's essential. While your underlying data structure does not have to be lists, functional programming involves building up systems from functions that operate on your data. (The well-known Alan Perlis quote comes to mind: "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.") The basic functions involved definitely belong in a library. There is plenty of room to discuss what should and what shouldn't be included, but providing nothing at all, and only providing functions to modify other functions makes no sense to me. It sounds like recursion without a base case.

4 on your list discusses "working with first-class functions." Given that functions in JS already are first-class, this really just means, "working with functions." This is all well and good. But what are those functions you're working with themselves working with? At some point, you need to start with something. And if that something involves lambdas you're going to lose a lot of expressiveness. The same is true with Underscore/Lodash. They are designed for a different paradigm. Given this (please forgive the Ramda usage; I don't know fn's API that well):

const square = n => n * n;

It's the difference between

const sum = R.reduce(R.add, 0);
const addSquares = R.compose(sum, R.map(square));

and

const sum = ns => ns.reduce(R.add, 0);
const addSquares = R.compose(sum, ns => ns.map(square));

I believe the latter is significantly less readable and clear. And it will get significantly less clear as more lambdas are used.


To me, this would be the death of the library. I would suggest that if you want to do this, fork the library with a different name, and leave fn.js to work more like it has. I don't know what sort of user-base fn.js has, but I can't see that this could do them any good.

CrowdHailer commented 8 years ago

@eliperelman in this case ramda would have been a better example of a project that overlaps with fn.js

@CrossEye I see you points but I would just like to see if I can convince you with a few counter points. 1) the example you gave of creating an addSquares function is not as simply as you imply because as its stands fn.js map function is not curried. 2) The fact you have used ramda as an example is significant. The old maintainer left this project saying that they were considering depreciating it in favour of ramda.

It's certainly possible that this project can die by a) not having a maintain or b) not having any reason to exist as all of its functionality is available in ramda.

3) I would certainly be interested in hearing more from the user-base of fn.js. However at the moment the only example I have is my usecase and that is a strong need for function modification functions.

The most interesting viewpoint in this discussion would be from someone who sometimes uses ramda sometimes fn.js and could provide insight to what the reasons for using each are.

eliperelman commented 8 years ago

@CrowdHailer agreed, but just because 2 libraries do the similar things doesn't mean they can't co-exist. fn.js doesn't autocurry and also sacrificies performance for the sake of internal purity as much as possible. Not that this was a good idea, but maybe it's important to someone. Take underscore/lodash for example. They both do they same thing with an identical API, and yet they both thrive.

  1. Currying the function afterwards is trivial, it's just not auto-curried.
  2. Ramda is basically a more fully-featured and convenient version of what fn.js is.
  3. I don't personally know anyone that used both Ramda and fn.js other than myself. Having generic utilities for functional programming is important, so changing the way the library works and its mantra for the sake of one use case that no one understands seems like the wrong direction.

I can't disagree with one thing that @CrossEye has said.

eliperelman commented 8 years ago

My thoughts are at this point: if you want to fundamentally shift the purpose and functionality of the library, then it is no longer fn.js. I'd agree with @CrossEye and say to fork and publish with a new name.

CrossEye commented 8 years ago

@CrowdHailer:

I'm having a difficult time disentangling two strands of thought in how I want to respond. The first is how this change would make me feel about the library personally: it would become a library I'm not interested in using. The second is what the change means for whoever does use it, and for the wider community; I believe it removes one of the few truly practical Javascript libraries designed on a certain set of simple principles.

As to the details, yes fn.js does not now auto-curry, but it sounded as though #20 is back in play, and I would hope that its functions will soon be curried. I used Ramda as an example because although I've been watching fn.js almost since its start, I really haven't been a user. I've been too busy on Ramda to do much more than watch the other libraries that interested me. But fn.js is one of the ones I've watched with greatest interest. It and FKit, and more recently fnuc, were the only ones that seemed to have the same principles. FKit stalled entirely some time ago, and while fnuc is quite interesting, it seems that fn is closer in spirit to Ramda.

I suppose that some might be happy to have their library gain major popularity, and wouldn't be sad to see competing libraries fade away, but @buzzdecafe and I did not set off to create a popular library. Ramda was a learning experience. I'd long promoted functional programming in JS, but it was a fairly anemic set of FP ideas I was concerned about until @buzzdecafe and I both read the first edition of @raganwald's JavaScript Allongé. I started a repo to play around with these ideas, and asked @buzzdecafe to join me almost immediately. It was our own playground for well over a year, known to us and a few friends, but not used anywhere, until we both were in a class taught by @DrBoolean and @begriffs where they had chosen Ramda as the library. At that point, we decided that if these guys thought it worth using, maybe we could talk about it. So we wrote some blog posts, and the library took off. We had to learn how to foster a FOSS community, which was not a goal we'd ever had. But it's been a fun ride.

Still, although I've enjoyed it all, I've wanted Ramda to do well only insofar as it's promoting good FP practices in the JS community. I'm thrilled that it's just had its millionth download, but I would stop using it immediately if there was a better tool for the type of coding I want to do. (Of course I might also try to improve Ramda to match, but that's not the point.)

Ramda is currently struggling with a difficult problem, one that might affect other similar libraries, an issue of dealing with the desire to use certain native optimizations when there would be no differences between value equality and reference equality, something that would substantially speed up our versions of functions such as uniq and intersection. It would be fantastic if we could look to other libraries that had the same types of concerns and see how they weighed the choices, look to any conversations they have had on such issues, speak to developers who've worked with the solutions they've come up with. And we'd like to be able to offer the same service to developers of other libraries.

I was not hoping for fn.js to necessarily be that alternative tool that we looked to, but to me having a good marketplace of related, but substantially different, tools, makes everyone better. The trouble here is not that this would be different; that's great. The problem is that this would shift off far enough that I would no longer find fn.js interesting enough to bother with. Perhaps I'm wrong. Maybe a library of plain function decorators with no data manipulation tools would be interesting on its own, but I don't see it.

Back to specifics, we discussed map. If you don't include a map function, and expect a user to work with the native Array.prototype.map or the Underscore version, then, assuming she would like to do the sort of coding that I care about, the style I mentioned above, she would have to do this:

const map = fn.curry((f, list) => list.map(f));

or this:

const map = fn.curry((f, list) => _.map(list, f));

and similar things for many other core functions. map, reduce, filter, contrary to what some people seem to think, do not constitute some mystical trinity of FP tools. But they are important functions, and making the user create them anew for each project seems a little silly to me.

So, as always, I'm way too verbose, and I apologize. I was so excited to see that fn.js was going to continue, and this change has got me crashing back to Earth.

This of course is your decision. I do hope you will consult with Eli. But I do understand that in shepherding a library, there are difficult decisions to make, and you won't be able to please everyone, not even cantankerous maintainers of competing libraries. So best of luck with it.

CrowdHailer commented 8 years ago

@CrossEye the verbosity is very helpful. Thanks for taking the time to reply.

It was certainly never my intention to be quite so controversial in my suggestions. I honestly thought the prime use for the functions above was in implementing the more interesting functions such as throttle. I thought that removing them would make the source code for fn.js very concise. To a degree that it would be approachable for beginners to see how well suited JS was to the manipulation of functions.

This would allow fn.js be an onramp to functional programming in JS. One of my other issues was to collect a list of similar libraries. I think it is a good thing when libraries acknowledge other similar libraries in the ecosystem (and not only so they can point out why they are better than them).

It is also not my expectation that users use Array.prototype.forEach directly. I personally use the forEach method as a hook to create a set of functionality for enumerables/collections. This has some parallels with the way elixir uses protocols and even more tenuously with the way rubys enumerable module works.

In this system each looks like the following

function each(fn, collection){
  var partial = function(collection){
    collection.forEach(fn);
  }
  return collection === void 0 ? partial : partial(collection);
}

This enumerable library obviously doesn't have functions like throttle etc hence I use fn.js. Also why I consider handling collections and handling functions to be two separate concerns.

I am taking on board all that's said and am really grateful for the time that has been spent on expressing your position. I still think that a library, 'to make functional programming good' dooms fn.js to growth. However if I will see how much sense I think there is in including just the functions mentioned.

If the consensus is that I am still taking fn.js too far from its roots. I will start my own project.