{"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":1,"created_at":"2015-07-08T10:44:35Z","updated_at":"2015-07-08T11:06: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/119542888","html_url":"https://github.com/fantasyland/fantasy-land/issues/97#issuecomment-119542888","issue_url":"https://api.github.com/repos/fantasyland/fantasy-land/issues/97","id":119542888,"user":{"login":"joneshf","id":1356417,"avatar_url":"https://avatars.githubusercontent.com/u/1356417?v=3","gravatar_id":"","url":"https://api.github.com/users/joneshf","html_url":"https://github.com/joneshf","followers_url":"https://api.github.com/users/joneshf/followers","following_url":"https://api.github.com/users/joneshf/following{/other_user}","gists_url":"https://api.github.com/users/joneshf/gists{/gist_id}","starred_url":"https://api.github.com/users/joneshf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joneshf/subscriptions","organizations_url":"https://api.github.com/users/joneshf/orgs","repos_url":"https://api.github.com/users/joneshf/repos","events_url":"https://api.github.com/users/joneshf/events{/privacy}","received_events_url":"https://api.github.com/users/joneshf/received_events","type":"User","site_admin":false},"created_at":"2015-07-08T11:06:18Z","updated_at":"2015-07-08T11:06:18Z","body":"The problem with using some arbitrary value for the empty is that now your function doesn't do the right thing for an empty `Foldable`. Like if you have an empty array or whatever, instead of getting back the right thing, you now get back a plain object.\r\n\r\nThere are at least two ways to handle this:\r\n\r\n1. Only operate on \"non-empty\" `Foldable`s--`Foldable1` as they're usually called in haskell and friends.\r\nIn this case, you can relax the constraint to `Semigroup`, and since you are guaranteed to have at least one element, you can always have an \"initial\" value.\r\n1. Take the `Monoid` as an argument, as you suggested. Although the api is slightly more cumbersome, it's \"The Right Way\". Let's not forget that a signature like:\r\n\r\n    ```haskell\r\n    foldMap :: (Foldable f, Monoid m) => (a -> m) -> f a -> m\r\n    ```\r\n\r\n    compiles to something like:\r\n\r\n    ```haskell\r\n    foldMap' :: foldableDict f -> monoidDict m -> (a -> m) -> f a -> m\r\n    ```\r\n\r\n    depending on the compiler. So there's no reason to expect js to have some way to circumvent the need to pass around the second dictionary."}}