
I find VimScript a very unfriendly, but unexpectedly powerful language. Fortunately, it is possible to befriend him, which I suggest you do in this series of articles. Here we will look at some solutions to typical problems in VimScript using the object-oriented paradigm, but we will not touch on the foundations of this language. Perhaps the article will be of interest to programmers who are interested in non-standard implementation of the solutions that have become familiar to us.
Object orientation may be minimalist.
Maybe some of you have already read my VimScript articles and have been studying my
vim_lib library, isn’t it really convenient and easy to use?
Not true ! Sometimes the "smells of the code" hurt my eyes so that I cannot read it. Even a weak “smell” makes me an overwhelming desire to make “better,” “more correctly,” “easier.” Fortunately, it is not difficult, it is still enough to simplify more and I did it, but now this is not about it. In this series of articles, I will only give sample solutions (patterns, if you please) of specific tasks, and I will not invent a new library.
For more than a year of using my class Object in VimScript, I made sure that it contains "code for a tick", which can be safely removed. When “such a smell” appears, it means that it is time to simplify everything. In particular, what can be safely abandoned when implementing an object-oriented model in VimScript:
')
- Classes - they are not as such. The class is reduced to a set of methods and a constructor that can create objects, extend them with these methods and initialize properties.
- Encapsulation - than to fence a bicycle crutch, it is easier to agree and not to use the properties of the object directly. Since the language does not implement encapsulation directly, do not torment it.
- Static properties and methods are a useful thing, but not so useful as to fill a constructor with conditions that select only non-static properties and methods for copying to an object. If static is needed, it is better to implement it as a global service.
Perhaps you are already wondering: "How do you implement an object-oriented model without classes?" - everything is extremely simple. For this we need one function for each type of object, which is called a constructor. This function should create and return us an initialized object with the desired structure. Reminds javascript, right? Here’s how it looks in finished form:
Base classlet s:Parent = {} function! s:Parent.new(a) dict return extend({'a': a:a}, s:Parent) endfunction function! s:Parent.setA(a) dict let l:self.a = a:a endfunction function! s:Parent.getA() dict return l:self.a endfunction let s:pobj = s:Parent.new('foo') echo s:pobj.getA() " foo
Four lines of code to implement the whole class. This solution is reduced to the initialization of a new dictionary and expansion (using the extend function) by its prototype methods.
Next, we consider the implementation of inheritance with overriding the constructor and one of the methods of the parent class:
Child class let s:Child = {} function! s:Child.new(a, b) dict return extend(extend({'b': a:b}, s:Parent.new(a:a)), s:Child) endfunction function! s:Child.setB(b) dict let l:self.b = a:b endfunction function! s:Child.getB() dict return l:self.b endfunction function! s:Child.getA() dict return call(s:Parent.getA, [], l:self) . l:self.b endfunction
In total, the constructor is supplemented by another call to the extend function, which allows the base dictionary to be expanded first with an object of the parent class and then with methods of the prototype (child class). In turn, the call to the parent method from the override is also quite simply implemented using the call function (the equivalent of apply in JavaScript).
Further inheritance is implemented without adding new extend calls:
Further inheritance let s:SubChild = {} function! s:SubChild.new(a, b, c) dict return extend(extend({'c': a:c}, s:Child.new(a:a, a:b)), s:SubChild) endfunction
Mixins
The attentive reader has already guessed that multiple inheritance is implemented here, which allows the use of mixins:
Further inheritance let s:Publisher = {} function! s:Publisher.new() dict return extend({'listeners': {}}, s:Publisher) endfunction let s:Class = {} function! s:Class.new() dict return extend(extend({}, s:Publisher.new()), s:Class) endfunction
Interfaces
Polymorphism is a very important part of the object-oriented paradigm, and I could not avoid it, especially since I have several plug-ins for which it is necessary. To make it a reality, you need an instanceof method that allows you to evaluate the semantics of a class. All that is required of him is to check whether the methods declared in the target class are present in the object and, if so, can be considered an instance of this class. Why methods, but not properties? Because we agreed to work with objects through methods. This is the so-called "
Duck typing ".
instanceof function! s:instanceof(obj, class) for l:assertFun in keys(filter(a:class, 'type(v:val) == 2')) if !has_key(a:obj, l:assertFun) return 0 endif endfor return 1 endfunction echo s:instanceof(s:childObject, s:Parent) " 1 echo s:instanceof(s:childObject, s:SubChild) " 0
Having such an excellent function, it is not difficult to implement interfaces that define the semantics of future classes:
Interface example let s:Iterable = {} function! s:Iterable.valid() dict endfunction function! s:Iterable.next() dict endfunction function! s:Iterable.current() dict endfunction let s:Iterator = {} function! s:Iterator.new(array) dict return extend(extend({'array': a:array, 'cursor': 0}, s:Iterable), s:Iterator) endfunction function! s:Iterator.valid() dict return exists('l:self.array[l:self.cursor]') endfunction function! s:Iterator.next() dict let l:self.cursor += 1 endfunction function! s:Iterator.current() dict return l:self.array[l:self.cursor] endfunction let s:iterator = s:Iterator.new([1,2,3]) echo s:instanceof(s:iterator, s:Iterable) " 1
It is important to remember that objects are extended by the class of the interface, and not by its instance. However, as in any other language.
JSON is easy!
For some, it will be a discovery, but JSON is VimScript's cousin! Do not believe? I'll prove it to you:
Json let s:childObj = s:Child.new(1, 2) let s:json = string(filter(s:childObj, 'type(v:val) != 2')) echo s:json " {'a': 1, 'b': 2} echo eval(s:json) == s:childObj " 1
Bye all
I hope this article will interest and encourage you to try this wonderful editor. If so, then I will try to make your life easier in the following articles.