| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 1 |  |
| 2 | |
| 3 | # What is Vim9? |
| 4 | |
| 5 | This is an experimental side of [Vim](https://github.com/vim/vim). |
| 6 | It explores ways of making Vim script faster and better. |
| 7 | |
| Bram Moolenaar | e7b1ea0 | 2020-08-07 19:54:59 +0200 | [diff] [blame] | 8 | WARNING: The Vim9 script features are still under development, anything can |
| 9 | break! |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 10 | |
| 11 | # Why Vim9? |
| 12 | |
| 13 | ## 1. FASTER VIM SCRIPT |
| 14 | |
| 15 | The third item on the poll results of 2018, after popup windows and text |
| 16 | properties, is faster Vim script. So how do we do that? |
| 17 | |
| 18 | I have been throwing some ideas around, and soon came to the conclusion |
| 19 | that the current way functions are called and executed, with |
| 20 | dictionaries for the arguments and local variables, is never going to be |
| 21 | very fast. We're lucky if we can make it twice as fast. The overhead |
| 22 | of a function call and executing every line is just too high. |
| 23 | |
| 24 | So what then? We can only make something fast by having a new way of |
| 25 | defining a function, with similar but different properties of the old |
| 26 | way: |
| 27 | * Arguments are only available by name, not through the a: dictionary or |
| 28 | the a:000 list. |
| 29 | * Local variables are not available in an l: dictionary. |
| 30 | * A few more things that slow us down, such as exception handling details. |
| 31 | |
| 32 | I Implemented a "proof of concept" and measured the time to run a simple |
| 33 | for loop with an addition (Justin used this example in his presentation, |
| 34 | full code is below): |
| 35 | |
| 36 | ``` vim |
| 37 | let sum = 0 |
| 38 | for i in range(1, 2999999) |
| 39 | let sum += i |
| 40 | endfor |
| 41 | ``` |
| 42 | |
| 43 | | how | time in sec | |
| 44 | | --------| -------- | |
| 45 | | Vim old | 5.018541 | |
| 46 | | Python | 0.369598 | |
| 47 | | Lua | 0.078817 | |
| 48 | | Vim new | 0.073595 | |
| 49 | |
| 50 | That looks very promising! It's just one example, but it shows how much |
| 51 | we can gain, and also that Vim script can be faster than builtin |
| 52 | interfaces. |
| 53 | |
| 54 | In practice the script would not do something useless as counting but change |
| Bram Moolenaar | e7b1ea0 | 2020-08-07 19:54:59 +0200 | [diff] [blame] | 55 | the text. For example, reindent all the lines: |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 56 | |
| 57 | ``` vim |
| 58 | let totallen = 0 |
| 59 | for i in range(1, 100000) |
| 60 | call setline(i, ' ' .. getline(i)) |
| 61 | let totallen += len(getline(i)) |
| 62 | endfor |
| 63 | ``` |
| 64 | |
| 65 | | how | time in sec | |
| 66 | | --------| -------- | |
| 67 | | Vim old | 0.853752 | |
| 68 | | Python | 0.304584 | |
| 69 | | Lua | 0.286573 | |
| 70 | | Vim new | 0.190276 | |
| 71 | |
| 72 | The differences are smaller, but Vim 9 script is clearly the fastest. |
| Bram Moolenaar | 9faec4e | 2021-02-27 16:38:07 +0100 | [diff] [blame] | 73 | Using LuaJIT gives 0.25, only a little bit faster than plain Lua. |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 74 | |
| 75 | How does Vim9 script work? The function is first compiled into a sequence of |
| 76 | instructions. Each instruction has one or two parameters and a stack is |
| 77 | used to store intermediate results. Local variables are also on the |
| 78 | stack, space is reserved during compilation. This is a fairly normal |
| 79 | way of compilation into an intermediate format, specialized for Vim, |
| 80 | e.g. each stack item is a typeval_T. And one of the instructions is |
| 81 | "execute Ex command", for commands that are not compiled. |
| 82 | |
| 83 | |
| Bram Moolenaar | 4f4d51a | 2020-10-11 13:57:40 +0200 | [diff] [blame] | 84 | ## 2. DEPRIORITIZE INTERFACES |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 85 | |
| 86 | Attempts have been made to implement functionality with built-in script |
| 87 | languages such as Python, Perl, Lua, Tcl and Ruby. This never gained much |
| 88 | foothold, for various reasons. |
| 89 | |
| 90 | Instead of using script language support in Vim: |
| 91 | * Encourage implementing external tools in any language and communicate |
| 92 | with them. The job and channel support already makes this possible. |
| 93 | Really any language can be used, also Java and Go, which are not |
| 94 | available built-in. |
| Bram Moolenaar | e7b1ea0 | 2020-08-07 19:54:59 +0200 | [diff] [blame] | 95 | * No priority for the built-in language interfaces. They will have to be kept |
| 96 | for backwards compatibility, but many users won't need a Vim build with these |
| 97 | interfaces. |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 98 | * Improve the Vim script language, it is used to communicate with the external |
| 99 | tool and implements the Vim side of the interface. Also, it can be used when |
| 100 | an external tool is undesired. |
| 101 | |
| Bram Moolenaar | 3d1cde8 | 2020-08-15 18:55:18 +0200 | [diff] [blame] | 102 | Altogether this creates a clear situation: Vim with the +eval feature |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 103 | will be sufficient for most plugins, while some plugins require |
| 104 | installing a tool that can be written in any language. No confusion |
| 105 | about having Vim but the plugin not working because some specific |
| 106 | language is missing. This is a good long term goal. |
| 107 | |
| 108 | Rationale: Why is it better to run a tool separately from Vim than using a |
| 109 | built-in interface and interpreter? Take for example something that is |
| 110 | written in Python: |
| 111 | * The built-in interface uses the embedded python interpreter. This is less |
| 112 | well maintained than the python command. Building Vim with it requires |
| 113 | installing developer packages. If loaded dynamically there can be a version |
| 114 | mismatch. |
| 115 | * When running the tool externally the standard python command can be used, |
| 116 | which is quite often available by default or can be easily installed. |
| 117 | * The built-in interface has an API that is unique for Vim with Python. This is |
| 118 | an extra API to learn. |
| 119 | * A .py file can be compiled into a .pyc file and execute much faster. |
| 120 | * Inside Vim multi-threading can cause problems, since the Vim core is single |
| 121 | threaded. In an external tool there are no such problems. |
| 122 | * The Vim part is written in .vim files, the Python part is in .py files, this |
| 123 | is nicely separated. |
| 124 | * Disadvantage: An interface needs to be made between Vim and Python. |
| 125 | JSON is available for this, and it's fairly easy to use. But it still |
| 126 | requires implementing asynchronous communication. |
| 127 | |
| 128 | |
| 129 | ## 3. BETTER VIM SCRIPT |
| 130 | |
| 131 | To make Vim faster a new way of defining a function needs to be added. |
| 132 | While we are doing that, since the lines in this function won't be fully |
| 133 | backwards compatible anyway, we can also make Vim script easier to use. |
| 134 | In other words: "less weird". Making it work more like modern |
| 135 | programming languages will help. No surprises. |
| 136 | |
| 137 | A good example is how in a function the arguments are prefixed with |
| 138 | "a:". No other language I know does that, so let's drop it. |
| 139 | |
| 140 | Taking this one step further is also dropping "s:" for script-local variables; |
| 141 | everything at the script level is script-local by default. Since this is not |
| 142 | backwards compatible it requires a new script style: Vim9 script! |
| 143 | |
| Bram Moolenaar | e7b1ea0 | 2020-08-07 19:54:59 +0200 | [diff] [blame] | 144 | To avoid having more variations, the syntax inside a compiled function is the |
| 145 | same as in Vim9 script. Thus you have legacy syntax and Vim9 syntax. |
| 146 | |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 147 | It should be possible to convert code from other languages to Vim |
| 148 | script. We can add functionality to make this easier. This still needs |
| 149 | to be discussed, but we can consider adding type checking and a simple |
| 150 | form of classes. If you look at JavaScript for example, it has gone |
| 151 | through these stages over time, adding real class support and now |
| 152 | TypeScript adds type checking. But we'll have to see how much of that |
| 153 | we actually want to include in Vim script. Ideally a conversion tool |
| 154 | can take Python, JavaScript or TypeScript code and convert it to Vim |
| 155 | script, with only some things that cannot be converted. |
| 156 | |
| 157 | Vim script won't work the same as any specific language, but we can use |
| 158 | mechanisms that are commonly known, ideally with the same syntax. One |
| 159 | thing I have been thinking of is assignments without ":let". I often |
| 160 | make that mistake (after writing JavaScript especially). I think it is |
| 161 | possible, if we make local variables shadow commands. That should be OK, |
| 162 | if you shadow a command you want to use, just rename the variable. |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 163 | Using "var" and "const" to declare a variable, like in JavaScript and |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 164 | TypeScript, can work: |
| 165 | |
| 166 | |
| 167 | ``` vim |
| 168 | def MyFunction(arg: number): number |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 169 | var local = 1 |
| 170 | var todo = arg |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 171 | const ADD = 88 |
| 172 | while todo > 0 |
| 173 | local += ADD |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 174 | todo -= 1 |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 175 | endwhile |
| 176 | return local |
| 177 | enddef |
| 178 | ``` |
| 179 | |
| 180 | The similarity with JavaScript/TypeScript can also be used for dependencies |
| 181 | between files. Vim currently uses the `:source` command, which has several |
| 182 | disadvantages: |
| 183 | * In the sourced script, is not clear what it provides. By default all |
| 184 | functions are global and can be used elsewhere. |
| 185 | * In a script that sources other scripts, it is not clear what function comes |
| 186 | from what sourced script. Finding the implementation is a hassle. |
| 187 | * Prevention of loading the whole script twice must be manually implemented. |
| 188 | |
| 189 | We can use the `:import` and `:export` commands from the JavaScript standard to |
| 190 | make this much better. For example, in script "myfunction.vim" define a |
| 191 | function and export it: |
| 192 | |
| 193 | ``` vim |
| 194 | vim9script " Vim9 script syntax used here |
| 195 | |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 196 | var local = 'local variable is not exported, script-local' |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 197 | |
| 198 | export def MyFunction() " exported function |
| 199 | ... |
| 200 | |
| 201 | def LocalFunction() " not exported, script-local |
| 202 | ... |
| 203 | ``` |
| 204 | |
| 205 | And in another script import the function: |
| 206 | |
| 207 | ``` vim |
| 208 | vim9script " Vim9 script syntax used here |
| 209 | |
| 210 | import MyFunction from 'myfunction.vim' |
| 211 | ``` |
| 212 | |
| 213 | This looks like JavaScript/TypeScript, thus many users will understand the |
| 214 | syntax. |
| 215 | |
| 216 | These are ideas, this will take time to design, discuss and implement. |
| 217 | Eventually this will lead to Vim 9! |
| 218 | |
| 219 | |
| 220 | ## Code for sum time measurements |
| 221 | |
| 222 | Vim was build with -O2. |
| 223 | |
| 224 | ``` vim |
| 225 | func VimOld() |
| 226 | let sum = 0 |
| 227 | for i in range(1, 2999999) |
| 228 | let sum += i |
| 229 | endfor |
| 230 | return sum |
| 231 | endfunc |
| 232 | |
| 233 | func Python() |
| 234 | py3 << END |
| 235 | sum = 0 |
| 236 | for i in range(1, 3000000): |
| 237 | sum += i |
| 238 | END |
| 239 | return py3eval('sum') |
| 240 | endfunc |
| 241 | |
| 242 | func Lua() |
| 243 | lua << END |
| 244 | sum = 0 |
| 245 | for i = 1, 2999999 do |
| 246 | sum = sum + i |
| 247 | end |
| 248 | END |
| 249 | return luaeval('sum') |
| 250 | endfunc |
| 251 | |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 252 | def VimNew(): number |
| 253 | var sum = 0 |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 254 | for i in range(1, 2999999) |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 255 | sum += i |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 256 | endfor |
| 257 | return sum |
| 258 | enddef |
| 259 | |
| 260 | let start = reltime() |
| 261 | echo VimOld() |
| 262 | echo 'Vim old: ' .. reltimestr(reltime(start)) |
| 263 | |
| 264 | let start = reltime() |
| 265 | echo Python() |
| 266 | echo 'Python: ' .. reltimestr(reltime(start)) |
| 267 | |
| 268 | let start = reltime() |
| 269 | echo Lua() |
| 270 | echo 'Lua: ' .. reltimestr(reltime(start)) |
| 271 | |
| 272 | let start = reltime() |
| 273 | echo VimNew() |
| 274 | echo 'Vim new: ' .. reltimestr(reltime(start)) |
| 275 | ``` |
| 276 | |
| 277 | ## Code for indent time measurements |
| 278 | |
| 279 | ``` vim |
| 280 | def VimNew(): number |
| Bram Moolenaar | 4466ad6 | 2020-11-21 13:16:30 +0100 | [diff] [blame] | 281 | var totallen = 0 |
| Bram Moolenaar | 8a7d654 | 2020-01-26 15:56:19 +0100 | [diff] [blame] | 282 | for i in range(1, 100000) |
| 283 | setline(i, ' ' .. getline(i)) |
| 284 | totallen += len(getline(i)) |
| 285 | endfor |
| 286 | return totallen |
| 287 | enddef |
| 288 | |
| 289 | func VimOld() |
| 290 | let totallen = 0 |
| 291 | for i in range(1, 100000) |
| 292 | call setline(i, ' ' .. getline(i)) |
| 293 | let totallen += len(getline(i)) |
| 294 | endfor |
| 295 | return totallen |
| 296 | endfunc |
| 297 | |
| 298 | func Lua() |
| 299 | lua << END |
| 300 | b = vim.buffer() |
| 301 | totallen = 0 |
| 302 | for i = 1, 100000 do |
| 303 | b[i] = " " .. b[i] |
| 304 | totallen = totallen + string.len(b[i]) |
| 305 | end |
| 306 | END |
| 307 | return luaeval('totallen') |
| 308 | endfunc |
| 309 | |
| 310 | func Python() |
| 311 | py3 << END |
| 312 | cb = vim.current.buffer |
| 313 | totallen = 0 |
| 314 | for i in range(0, 100000): |
| 315 | cb[i] = ' ' + cb[i] |
| 316 | totallen += len(cb[i]) |
| 317 | END |
| 318 | return py3eval('totallen') |
| 319 | endfunc |
| 320 | |
| 321 | new |
| 322 | call setline(1, range(100000)) |
| 323 | let start = reltime() |
| 324 | echo VimOld() |
| 325 | echo 'Vim old: ' .. reltimestr(reltime(start)) |
| 326 | bwipe! |
| 327 | |
| 328 | new |
| 329 | call setline(1, range(100000)) |
| 330 | let start = reltime() |
| 331 | echo Python() |
| 332 | echo 'Python: ' .. reltimestr(reltime(start)) |
| 333 | bwipe! |
| 334 | |
| 335 | new |
| 336 | call setline(1, range(100000)) |
| 337 | let start = reltime() |
| 338 | echo Lua() |
| 339 | echo 'Lua: ' .. reltimestr(reltime(start)) |
| 340 | bwipe! |
| 341 | |
| 342 | new |
| 343 | call setline(1, range(100000)) |
| 344 | let start = reltime() |
| 345 | echo VimNew() |
| 346 | echo 'Vim new: ' .. reltimestr(reltime(start)) |
| 347 | bwipe! |
| 348 | ``` |