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