{"action":"created","issue":{"url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/97","labels_url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/97/labels{/name}","comments_url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/97/comments","events_url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/97/events","html_url":"https://github.com/fantasyland/fantasy-land/issues/97","id":93759928,"number":97,"title":"Implementing `foldMap` for foldable","user":{"login":"paldepind","id":521604,"avatar_url":"https://avatars.githubusercontent.com/u/521604?v=3","gravatar_id":"","url":"https://api.github.com/users/paldepind","html_url":"https://github.com/paldepind","followers_url":"https://api.github.com/users/paldepind/followers","following_url":"https://api.github.com/users/paldepind/following{/other_user}","gists_url":"https://api.github.com/users/paldepind/gists{/gist_id}","starred_url":"https://api.github.com/users/paldepind/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paldepind/subscriptions","organizations_url":"https://api.github.com/users/paldepind/orgs","repos_url":"https://api.github.com/users/paldepind/repos","events_url":"https://api.github.com/users/paldepind/events{/privacy}","received_events_url":"https://api.github.com/users/paldepind/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":19,"created_at":"2015-07-08T10:44:35Z","updated_at":"2015-07-09T17:29:18Z","closed_at":null,"body":"With a foldable it is (or should be) possible to implement `foldMap` in terms of `foldr` (what fantasy land calls `reduce`) and vice versa.\r\n\r\nIf I have a monoid like this:\r\n\r\n```javascript\r\nfunction _Add(n) {\r\n  this.n = n;\r\n}\r\n_Add.prototype.empty = function() {\r\n  return new _Add(0);\r\n}\r\n_Add.prototype.concat = function(m) {\r\n  return new _Add(this.n + m.n);\r\n}\r\nfunction Add(n) {\r\n  return new _Add(n);\r\n}\r\n```\r\n\r\n`foldMap` should behave like this when given a traversable of numbers and the above constructor function.\r\n\r\n```javascript\r\nassert.deepEqual(foldMap(Add, traversableFromArray([1, 2, 3, 4])), Add(10));\r\n```\r\n\r\nBut the straightforward implementation doesn't work:\r\n\r\n```javascript\r\nfunction foldMap(f, t) {\r\n  t.reduce(f, empty);\r\n  //          ╰── does not work, there is no empty\r\n}\r\n```\r\n\r\nThe problem is that `foldMap` doesn't know how to create an identity element of the monoid inside the passed foldable. The easiest solution would be to do the same as what fantasy land does in a few places and demand an additional argument to help the implementation. In this case if users where required to pass an empty value then the simple implementation would work.\r\n\r\nBut such an argument results in an inferior API with unfortunate ugliness.\r\n\r\nSince the function passed to `foldMap` is required to return a monoid it is possible, with a bit of clunkiness, to work around the need for an initial empty value. An implementation that satisfies the previous assertion is given below:\r\n\r\n```javascript\r\n    var empty = {};\r\n    function foldMap(f, t) {\r\n      return t.reduce(function(acc, n) {\r\n        return acc === empty ? f(n) : concat(acc, f(n));\r\n      }, empty);\r\n    }\r\n```\r\n\r\nWe create a dummy empty value that contains a pointer to an object (which we now will be unique) and uses it inside the function passed to `reduce` to ignore the initial accumulator value.\r\n\r\nThis seems to result in the desired `foldMap` at the cost of elegance in the implementation.\r\n\r\nAny thoughts?"},"comment":{"url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/comments/120079940","html_url":"https://github.com/fantasyland/fantasy-land/issues/97#issuecomment-120079940","issue_url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/97","id":120079940,"user":{"login":"CrossEye","id":151263,"avatar_url":"https://avatars.githubusercontent.com/u/151263?v=3","gravatar_id":"","url":"https://api.github.com/users/CrossEye","html_url":"https://github.com/CrossEye","followers_url":"https://api.github.com/users/CrossEye/followers","following_url":"https://api.github.com/users/CrossEye/following{/other_user}","gists_url":"https://api.github.com/users/CrossEye/gists{/gist_id}","starred_url":"https://api.github.com/users/CrossEye/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CrossEye/subscriptions","organizations_url":"https://api.github.com/users/CrossEye/orgs","repos_url":"https://api.github.com/users/CrossEye/repos","events_url":"https://api.github.com/users/CrossEye/events{/privacy}","received_events_url":"https://api.github.com/users/CrossEye/received_events","type":"User","site_admin":false},"created_at":"2015-07-09T17:29:18Z","updated_at":"2015-07-09T17:29:18Z","body":"@paldepind:\r\n> This `flatMap` require an object with an `empty` method so users can either pass the prototype, the constructor or an instance. It doesn't matter to the point I'm trying to make: `flatMap` can be implemented with the current spec.\r\n\r\n`foldMap`, surely?\r\n\r\nIf all the user is supposed to pass in is something that has an `emtpy()` method, and if it's only going to be used once, might it not be better just for them to pass an/the empty instance directly?  I know you mentioned this above, but that seems cleaner than the suggestion that you can pass any instance or the constructor, so long as whatever you pass has `empty()` defined on it.\r\n\r\nIn the end, though, I think to your fundamental question, the answer is yes, you have to supply something more than the function and the Foldable in order to handle the empty case.  This shouldn't be such a surprise:  `foldl`/`foldr` do have their `foldl1`/`foldr1` counterparts that don't need that third parameter, but then throw errors if their Foldables are empty.\r\n\r\n> I still don't get it. What is the advantage of this compared to just using a `concat` method?\r\n\r\nPerhaps there's none.  It seems like a really good idea to me, but I haven't proven it, even to myself yet..  The thought is that I'd rather be programming like this:\r\n\r\n```js\r\n    var myMonoidImpl3 = concat(myMonoidImpl1, myMonoidImpl2);\r\n    var maybeSquare = pipe(getNextValue, Maybe.of, map(square))(currentVal);\r\n    var foldMap = curry(function(monoid, fn, foldable) {\r\n      return reduce(function(acc, n) {\r\n        concat(acc, f(n));\r\n      }, empty, foldable);\r\n    });\r\n    var result = foldMap(fn, MyMonoid.empty, myTreeOfMyMonoids)\r\n```\r\n\r\nthan like this:\r\n\r\n```js\r\n    var myMonoidImpl3 = myMonoidImpl1.concat(myMonoidImpl2);\r\n    var maybeSquare = pipe(getNextValue, Maybe.of, val => val.map(square))(currentVal);\r\n    function foldMap(monoid, f, foldable) {\r\n      return foldable.reduce(function(acc, n) {\r\n        return acc.concat(f(n));\r\n      }, monoid.empty());\r\n    }\r\n    var result = foldMap(MyMonoid, fn, myTreeOfMyMonoids)\r\n```\r\n\r\nSuch an implementation would help me work in this preferred manner.  We'll see if I can make it work, make it usable, and make it efficient.  But that API is the goal."}}