{"action":"created","issue":{"url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/issues/79","repository_url":"https://api.github.com/repos/sanctuary-js/sanctuary-def","labels_url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/issues/79/labels{/name}","comments_url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/issues/79/comments","events_url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/issues/79/events","html_url":"https://github.com/sanctuary-js/sanctuary-def/pull/79","id":162311392,"number":79,"title":"define $.Function and $.UnaryTypeVariable","user":{"login":"davidchambers","id":210406,"avatar_url":"https://avatars.githubusercontent.com/u/210406?v=3","gravatar_id":"","url":"https://api.github.com/users/davidchambers","html_url":"https://github.com/davidchambers","followers_url":"https://api.github.com/users/davidchambers/followers","following_url":"https://api.github.com/users/davidchambers/following{/other_user}","gists_url":"https://api.github.com/users/davidchambers/gists{/gist_id}","starred_url":"https://api.github.com/users/davidchambers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/davidchambers/subscriptions","organizations_url":"https://api.github.com/users/davidchambers/orgs","repos_url":"https://api.github.com/users/davidchambers/repos","events_url":"https://api.github.com/users/davidchambers/events{/privacy}","received_events_url":"https://api.github.com/users/davidchambers/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2016-06-26T04:03:04Z","updated_at":"2016-06-26T16:14:54Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/pulls/79","html_url":"https://github.com/sanctuary-js/sanctuary-def/pull/79","diff_url":"https://github.com/sanctuary-js/sanctuary-def/pull/79.diff","patch_url":"https://github.com/sanctuary-js/sanctuary-def/pull/79.patch"},"body":"__TL;DR__ we can now do this:\r\n\r\n```javascript\r\nconst a = $.TypeVariable('a');\r\nconst b = $.TypeVariable('b');\r\nconst f = $.UnaryTypeVariable('f');\r\n\r\n//    Functor :: TypeClass\r\nconst Functor = ...;\r\n\r\n//    map :: Functor f => (a -> b) -> f a -> f b\r\nconst map =\r\ndef('map',\r\n    {f: [Functor]},\r\n    [$.Function([a, b]), f(a), f(b)],\r\n    (fn, functor) => functor.map(fn));\r\n```\r\n\r\n---\r\n\r\n_This pull request sits atop #78 and should not be merged until #78 has been merged._\r\n\r\n_sanctuary-js/sanctuary#232 is heavily dependent on this pull request._\r\n\r\nThis pull request adds support for what I believe to be the two missing pieces of the puzzle:\r\n\r\n  - higher-order functions; and\r\n  - parameterized type variables.\r\n\r\nA wonderful consequence of this change is that type signatures in error messages will now match the type signatures which appear in documentation – no more `map :: Function -> a -> b`!\r\n\r\n#### `$.Function :: Array Type -> Type`\r\n\r\nThe type formerly known as `$.Function` is now known as `$.AnyFunction`. `$.Function` is now a *constructor* for function types.\r\n\r\nExamples:\r\n\r\n  - `$.Function([$.Date, $.String])` represents the `Date -> String` type; and\r\n  - `$.Function([a, b, a])` represents the `(a, b) -> a` type.\r\n\r\n```javascript\r\n//    formatDates :: (Date -> String) -> Array Date -> String\r\nconst formatDates =\r\ndef('formatDates',\r\n    {},\r\n    [$.Function([$.Date, $.String]), $.Array($.Date), $.String],\r\n    (f, dates) => implementation);\r\n```\r\n\r\nWhat will happen when one evaluates `formatDates(d => d.toISOString(), [new Date(0)])`?\r\n\r\n1.  A check will be performed to confirm that `d => d.toISOString()` is in fact a function (of some sort).\r\n\r\n2.  A check will be performed to confirm that `[new Date(0)]` is in fact an array.\r\n\r\n3.  A check will be performed to confirm that `new Date(0)` is in fact a Date object.\r\n\r\n4.  `f` will be decorated along these lines:\r\n\r\n    ```javascript\r\n    function(x) {\r\n      if (arguments.length !== 1) throw new TypeError('...');\r\n      if (!$.Date._test(x)) throw new TypeError('...');\r\n      var output = f(x);\r\n      if (!$.String._test(output)) throw new TypeError('...');\r\n      return output;\r\n    }\r\n    ```\r\n\r\n5.  `implementation` will be applied to the above function and `dates`.\r\n\r\n`$.Function` takes an array of types just as `def` does. `$.Function` can thus be used to represent functions of any arity (including nullary functions). The downside of this approach is that representing curried functions is awkward: `a -> b -> a`, for example, is `$.Function([a, $.Function([b, a])])`. The solution is to define a binary type constructor:\r\n\r\n```javascript\r\n//    Fn :: (Type, Type) -> Type\r\nconst Fn = (x, y) => $.Function([x, y]);\r\n```\r\n\r\nOne can then express `a -> b -> a` as `Fn(a, Fn(b, a))`.\r\n\r\n#### `$.UnaryTypeVariable :: String -> Type -> Type`\r\n\r\nThis essentially combines [`$.TypeVariable`][1] and [`$.UnaryType`][2]. See the readme for details.\r\n\r\n#### Limitations\r\n\r\n  - Only functions at the top level of the types array are wrapped.\r\n\r\n    The function in `[$.Array($.Function([a, b])), $.Array(a), $.Array(b)]`, for example, will not be wrapped. I hope to address this limitation in the future.\r\n\r\n#### Breaking API changes\r\n\r\n  - `$.Function` has been repurposed. When upgrading the most straightforward substitution is `:%s/\\$\\.\\<Function\\>/$.AnyFunction/g`, but in most cases one will wish to faithfully describe the types of one's higher-order functions (using the new `$.Function`).\r\n\r\n  - “Extractors” of parameterized types are no longer exposed via `._1` and `._2`. They are now exposed via `.types.$1.extractor` and `.types.$2.extractor`.\r\n\r\n  - The inner types of parameterized types are no longer exposed via `.$1` and `.$2`. They are now exposed via `.types.$1.type` and `.types.$2.type`.\r\n\r\n  - RecordType values no longer expose `.fields`. The type of the `x` field, for example, was previously exposed via `.fields.x`. It is now exposed via `.types.x.type`. The similarity between RecordType values and values of parameterized types is intentional, and allows code sharing where previously branching logic was required. If one squints one can see a type such as `Pair a b` as a record type: `{ $1 :: a, $2 :: b }`.\r\n\r\n#### Style changes\r\n\r\n  - In accordance with sanctuary-js/sanctuary#180 `[a]` is now written `Array a`.\r\n\r\n  - In accordance with sanctuary-js/sanctuary#214 an ES5-compatible environment is now assumed, so the implementation makes use of `Array#map`, `Object.keys`, and other useful ES5 functions.\r\n\r\n  - In accordance with sanctuary-js/sanctuary#225 the test suite now defines `Nothing :: Maybe a` rather than `Nothing :: -> Maybe a`.\r\n\r\n#### Refactoring\r\n\r\nIt took many, many hours but I was successful in creating a function, `createType`, which makes it clear what is required of a type and removes duplication.\r\n\r\n`$.TypeVariable`, before:\r\n\r\n```javascript\r\n//  TypeVariable :: String -> Type\r\n$.TypeVariable = function(name) {\r\n  return {\r\n    '@@type': 'sanctuary-def/Type',\r\n    type: 'VARIABLE',\r\n    name: name,\r\n    validate: Right,\r\n    _test: K(true),\r\n    toString: always(name)\r\n  };\r\n};\r\n```\r\n\r\n`$.TypeVariable`, after:\r\n\r\n```javascript\r\n//  TypeVariable :: String -> Type\r\n$.TypeVariable = function(name) {\r\n  return createType(VARIABLE, name, always2(name), K(true), [], {});\r\n};\r\n```\r\n\r\n`$.UnaryType`, before:\r\n\r\n```javascript\r\n//  UnaryType :: (String, (x -> Boolean), (t a -> [a])) -> Type -> Type\r\nvar UnaryType = $.UnaryType = function(name, test, _1) {\r\n  return function($1) {\r\n    var format = function(f, f$1) {\r\n      return f('(' + stripNamespace(name) + ' ') + f$1(String($1)) + f(')');\r\n    };\r\n    var validate = function(x) {\r\n      if (!test(x)) {\r\n        return Left({value: x, typePath: [t], propPath: []});\r\n      }\r\n      for (var idx = 0, xs = _1(x); idx < xs.length; idx += 1) {\r\n        var result = $1.validate(xs[idx]);\r\n        if (result.isLeft) {\r\n          return Left({value: result.value.value,\r\n                       typePath: [t].concat(result.value.typePath),\r\n                       propPath: ['$1'].concat(result.value.propPath)});\r\n        }\r\n      }\r\n      return Right(x);\r\n    };\r\n    var t = {\r\n      '@@type': 'sanctuary-def/Type',\r\n      type: 'UNARY',\r\n      name: name,\r\n      validate: validate,\r\n      _test: testFrom(validate),\r\n      format: format,\r\n      toString: always(format(id, id)),\r\n      _1: _1,\r\n      $1: $1\r\n    };\r\n    return t;\r\n  };\r\n};\r\n```\r\n\r\n`$.UnaryType`, after:\r\n\r\n```javascript\r\n//  UnaryType :: (String, (x -> Boolean), (t a -> Array a)) -> Type -> Type\r\nvar UnaryType = $.UnaryType = function(name, test, _1) {\r\n  return function($1) {\r\n    var format = function(outer, inner) {\r\n      return outer('(' + stripNamespace(name) + ' ') +\r\n             inner('$1')(String($1)) + outer(')');\r\n    };\r\n    var types = {$1: {extractor: _1, type: $1}};\r\n    return createType(UNARY, name, format, test, ['$1'], types);\r\n  };\r\n};\r\n```\r\n\r\n`$.Function` and `$.UnaryTypeVariable` are also defined via `createType`.\r\n\r\n---\r\n\r\nThis is a great opportunity to familiarize yourself with this codebase if you're interested but haven't known where to begin. The code should be easier to follow now, though there are still several functions which take six, seven, or even nine (!) arguments without many comments beyond the type annotations. I'm happy to answer questions; please let me know if anything is unclear. Future readers (including future me) will appreciate anything we can do now to clarify the code.\r\n\r\n\r\n[1]: https://github.com/sanctuary-js/sanctuary-def#typevariable\r\n[2]: https://github.com/sanctuary-js/sanctuary-def#unarytype\r\n"},"comment":{"url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/issues/comments/228608878","html_url":"https://github.com/sanctuary-js/sanctuary-def/pull/79#issuecomment-228608878","issue_url":"https://api.github.com/repos/sanctuary-js/sanctuary-def/issues/79","id":228608878,"user":{"login":"svozza","id":8573472,"avatar_url":"https://avatars.githubusercontent.com/u/8573472?v=3","gravatar_id":"","url":"https://api.github.com/users/svozza","html_url":"https://github.com/svozza","followers_url":"https://api.github.com/users/svozza/followers","following_url":"https://api.github.com/users/svozza/following{/other_user}","gists_url":"https://api.github.com/users/svozza/gists{/gist_id}","starred_url":"https://api.github.com/users/svozza/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/svozza/subscriptions","organizations_url":"https://api.github.com/users/svozza/orgs","repos_url":"https://api.github.com/users/svozza/repos","events_url":"https://api.github.com/users/svozza/events{/privacy}","received_events_url":"https://api.github.com/users/svozza/received_events","type":"User","site_admin":false},"created_at":"2016-06-26T16:14:54Z","updated_at":"2016-06-26T16:14:54Z","body":"This looks great, David! I should have a chance to dig into the code tomorrow evening."}}
{"actor":{"display_login":"svozza"}}