└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Babel syntax proposal: Double colon type declarations 2 | 3 | JavaScript / Flow syntax proposal. Separate declaration and implementation of functions, variables, ... in order to improve developer experience when writing typed JS code. 4 | 5 | The idea for this syntax has been inspired by the syntax of [elm](http://elm-lang.org/), [haskell](http://www.haskell.org/) and the comments in the snippets shown in [JavaScript without loops](http://jrsinclair.com/articles/2017/javascript-without-loops/). 6 | 7 | Check out [Professor Frisby's Mostly Adequate Guide to Functional Programming, Chapter 7](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch7.html) if you seek further reading. The guide is about JavaScript, using the same syntax, but just in comments. 8 | 9 | *Disclaimer: Like many language features this one is also subject to personal preference and you might or might not like it.* 10 | 11 | 12 | ## What it is 13 | 14 | Static typing in JavaScript is a nice and powerful opt-in feature. Both TypeScript and Flow use the same syntax of inlining the type declarations as part of the actual implementation code. While that works well in general, it also tends to make the code slightly harder to read, as it happens in C, C++, Java, ..., too. 15 | 16 | There is a different way favored by popular functional programming languages which chose to go a different way: Separating the declaration and the implementation. 17 | 18 | This proposal covers adding this syntax to JavaScript using the `::` operator: 19 | 20 | ```js 21 | minimum :: (...number) => number 22 | 23 | function minimum (...args) { 24 | return args.reduce( 25 | (min, arg) => arg < min ? arg : min, 26 | 0 27 | ) 28 | } 29 | ``` 30 | 31 | ## Benefits 32 | 33 | 1. It is easier to add to existing non-typed code, since you do not even need to touch the actual code. You just need to add the declaration. 34 | 35 | 2. It becomes trivial to track / recognize API changes. As soon as you split declaration and implementation you can track breaking interface changes by just watching the declarations and use this knowledge for semantic versioning. Elm, for example, does automatic semantic versioning based on this. 36 | 37 | 3. For functions having many parameters with complex types it can increase readability, since a medium-length declaration line and a medium-length implementation line are easier to grasp than a single long line including both. 38 | 39 | 4. When passing an object as a function parameter you can specify the object properties' types more elegantly. Consider a functional React component, for instance: 40 | 41 | ```js 42 | Greeting :: ({ name: string }) => React.Element 43 | 44 | function Greeting ({ name }) { 45 | return
Hello, {name}!
46 | } 47 | 48 | // You could not write `function Greeting ({ name: string }): React.Element {`, 49 | // since `{ name: string }` would be interpreted as a `const string = props.name`. 50 | ``` 51 | 52 | 53 | ## What it is not 54 | 55 | This syntax is intended to be consumed by flow/babel and other code analyzers/transpilers. It is not intended to become an ES core language feature. 56 | 57 | 58 | ## Try it yourself 59 | 60 | Check out [gear](https://github.com/andywer/gear). It is a small tool that makes Babel and Flow work with those types. Futhermore its own code uses this very syntax extension, so check out its code to see a working example of code typed this way. 61 | 62 | 63 | ## Manual setup 64 | 65 | Babel as well as its parser Babylon had to be patched/extended in order to parse the double-colon type syntax. You can find the Babylon fork [here](https://github.com/andywer/babylon/tree/feature/dblcolon-types). Clone the feature branch and run `yarn && npm run build && npm link`. 66 | 67 | The Babel fork can be found [here](https://github.com/andywer/babel/tree/feature/dctypes). Clone the feature branch and run `yarn && lerna bootstrap && npm run build` in the project root directory, then `cd packages/babel-core && rm -rf node_modules/babylon && npm link babylon`. Finally `cd ../babel-types && npm link`. 68 | 69 | To set up the samples in `dctypes-test/` in the babel root directory: 70 | 71 | ```sh 72 | cd dctypes-test/ 73 | npm install 74 | rm -rf node_modules/babylon node_modules/babel-types 75 | npm link babylon 76 | npm link babel-types 77 | ``` 78 | 79 | `npm run` will show you the samples to run. 80 | 81 | 82 | ## Available transformations 83 | 84 | ### babel-plugin-transform-to-flow 85 | 86 | #### Source 87 | 88 | ```js 89 | // test-code.js 90 | 91 | name :: string 92 | 93 | let name = 'Harry' 94 | 95 | add :: (number, number) => number 96 | 97 | function add (x, y) { 98 | return x + y 99 | } 100 | ``` 101 | 102 | #### Command 103 | 104 | ```sh 105 | babel --plugins=transform-dctypes-to-flow --no-babelrc test-code.js 106 | ``` 107 | 108 | #### Output 109 | 110 | ```js 111 | /* @flow */ 112 | 113 | let name: string = 'Harry'; 114 | 115 | function add(x: number, y: number): number { 116 | return x + y; 117 | } 118 | ``` 119 | 120 | ### babel-plugin-transform-dctypes-comments 121 | 122 | #### Source 123 | 124 | ```js 125 | // test-code.js 126 | 127 | add :: (number, number) => number 128 | 129 | function add (x, y) { 130 | return x + y 131 | } 132 | ``` 133 | 134 | #### Command 135 | 136 | ```sh 137 | babel --plugins=transform-dctypes-comments --no-babelrc test-code.js 138 | ``` 139 | 140 | #### Output 141 | 142 | ```js 143 | 144 | // add :: (number, number) => number 145 | function add(x, y) { 146 | return x + y; 147 | } 148 | ``` 149 | 150 | ### babel-plugin-transform-dctypes-to-flow & babel-plugin-flow-runtime 151 | 152 | #### Source 153 | 154 | ```js 155 | // test-code.js 156 | 157 | add :: (number, number) => number 158 | 159 | function add (x, y) { 160 | return x + y 161 | } 162 | ``` 163 | 164 | #### Command 165 | 166 | ```sh 167 | NODE_ENV=development babel --plugins=transform-dctypes-to-flow,flow-runtime --no-babelrc test-code.js 168 | ``` 169 | 170 | #### Output 171 | 172 | ```js 173 | /* @flow */import t from "flow-runtime"; 174 | 175 | 176 | function add(x: number, y: number): number { 177 | let _xType = t.number(); 178 | 179 | let _yType = t.number(); 180 | 181 | const _returnType = t.return(t.number()); 182 | 183 | t.param("x", _xType).assert(x); 184 | t.param("y", _yType).assert(y); 185 | 186 | return _returnType.assert(x + y); 187 | } 188 | t.annotate(add, t.function(t.param("x", t.number()), t.param("y", t.number()), t.return(t.number()))); 189 | ``` 190 | 191 | 192 | ## Limitations 193 | 194 | - Does not yet work with `flow-bin`. The static flow type checker is written in OCAML and it would need to be patched as well. 195 | - Just a proof-of-concept right now. Don't be too mad if it does not work in edge-cases yet. 196 | 197 | 198 | ## Feedback 199 | 200 | Having feedback? Feel free to open an issue or pull request 😉 201 | --------------------------------------------------------------------------------