En capítulos previos, has visto en acción las funciones nativas de Vimscript (`len()`, `filter()`, `map()`, etc.) y las funciones personalizadas. En este capítulo, vas a profundizar en aprender cómo funcionan las funciones.
Una definición de una función debe comenzar con una letra mayúscula. Comienza con la palabra clave `function` y termina con `endfunction`. A continuación veremos un ejemplo de una función válida:
Si antepones una función con el prefijo que la marca como una variable de script (`s:`), puedes declararla con una letra minúscula . `function s:sabrosa()` es un nombre válido. La razón de porque Vim necesita que se utilice un nombre con la letra inicial en mayúscula es para prevenir una confusión con las funciones propias de Vim (que son todas en minúsculas).
Un nombre de función no puede comenzar con un número. `1Sabrosa()` no es un nombre de función válido, pero `Sabrosa1()` si lo es. Una función tampoco puede contener caracteres no alfanuméricos menos `_`. `Sabrosa-comida()`, `Sabrosa&comida()` o `Sabrosa.comida()` todos estos son ejemplos de nombres no válidos para nombres de funciones. `Sabrosa_comida()`*si lo es*.
Si defines dos funciones con el mismo nombre, Vim mostrará un error quejándose de que la función `Sabrosa` ya existe. Para sobrescribit la función previa con el mismo nombre, añade un símbolo `!` después de la palabra clave `function`.
Para ver todas las funciones propias y personalizadas en Vim, puedes ejecutar el comando `:function`. Para ver el contenido de la función `Sabrosa`, puedes ejecutar `:function Sabrosa`.
También puedes buscar funciones con un patrón mediante `:function /patrón`, de manera similar a la navegación de búsqueda con Vim (`/patrón`). Para buscar todas las funciones que contengan la palabra "map", ejecuta `:function /map`. Si utilizas complementos externos, Vim mostrará las funciones definidas en esos complementos.
Si quieres ver dónde se origina una función, puedes utilizar el comando `:verbose` junto con el comando `:function`. Para buscar dónde se originan todas las funciones que contienen la palabra "map", ejecuta:
Cuando lo ejecuto, obtengo un número de resultados. Esto me dice que la función `fzf#vim#maps` una función de autoload (vuelve a echar un vitazo al capítulo 23) está escrita dentro del archivo `~/.vim/plugged/fzf.vim/autoload/fzf/vim.vim`, en la línea 1263. Esto es muy útil a la hora de depurar errores.
Para que una función devuelva un valor, necesitas pasarle de manera explícita un valor `return`. De otra manera, Vim automáticamente devuelve de manera implícita un valor 0.
Si ejecutas `:echo Sabrosa()` utilizando la función anterior, después de que Vim muestre la palabra "Sabrosa", devolverá un 0, el valor implícito de retorno. Para hacer que la función `Sabrosa()` devuelva el valor "Sabrosa", puedes hacer lo siguiente:
Puedes utilizar una función dentro de una expresión. Vim utilizará el valor de retorno de esa función. La expresión `:echo Sabrosa() . " comida."` mostrará "Sabrosa comida."
`a:` es uno de los ámbitos de las variables mencionados en el capítulo anterior. Es el de variable de parámtero formal. Es la manera de Vim para obtener un parámetro formal en una función. Sin este, Vim mostrará un errror:
En este contexto, la variable `sitio` es lo mismo que `l:sitio`. Cuando defines una variable en una función, es variable es *local* a esa función. Cuando un usuario ve `sitio`, podría fácilmente caer en el error de que se trata de una variable global. Por eso prefiero no escatimar en información y siempre añadir `l:` para indicar que es una variable propia de la función.
Otra razón para utilizar el método `l:count` al declarar una variable propia de una función es que Vim tiene unas variables especiales con alias que son similares a una variable normal. `v:count` es un ejemplo. Esta variable tien un alias `count`. En Vim, llamar a `count` es lo mismo que que llamar a `v:count`. Es muy sencillo llamar de manera accidental a una de esas variables especiales.
La ejecución anterior muestra un error, porque `let count = "count"` de manera implícita trata de redefinir una variable especial de Vim `v:count`. Recuerda que las variables especiales (`v:`) son de solo lectura. No las puedes cambiar. Para solucionar esto, utiliza `l:count`:
¡Vaya! obtuviste un error. El comando `call` anterior es un comando de la línea de comandos (`:call`). El comando `echo` anterior es también un comando para la línea de comandos (`:echo`). No puedes llamar a un comando para la línea de comandos con otro comando para la línea de comandos. Vamos a utilizar otra opción con el comando `call`:
Para aclarar cualquier confusión, acabas de utilizar dos comandos `call` diferentes: el comando `:call` para la línea de comandos y la función `call()`. La función `call()` acepta como su primer argumento el nombre de la función (una cadena) y su segundo argumento los parámetros formales (una lista).
Puedes crear una función con un valor predeterminado con `=`. Si llamas a la función `Desayuno` con solo un argumento, el argumento `bebida` usará el valor predeterminado "leche".
Puedes pasarle una variable a un argumento mediante tres puntos (`...`). Un argumento de variable es útil cuando no sabes cuantas variables dará un usuario.
Si ejecutas `echo Buffet("Fideos")`, entonces mostrará "Fideos". Vim utiliza `a:1` para mostrar el *primer* argumento que se le pasa a la función hasta `...`, más de 20 (`a:1` es el primer argumento, `a:2` es el segundo argumento, etc). Si ejecutas `echo Buffet("Fideos", "Sushi")`, mostrará solo "Fideos", vamos a actualizar la función:
El problema con este método es que si ejecutas `echo Buffet("Fideos")` (con solo una variables), Vim se quejará de que tiene una variable no definida en `a:2`. ¿Cómo podemos hacerlo lo suficientemente flexible para mostrar exactamente lo que el usuario le de a la función?
Las llaves `a:{l:contador_comida}` es una interpolación de una cadena, utiliza el valor del contador `contador_comida` para llamar a los argumentos del parámetro formal `a:1`, `a:2`, `a:3`, etc.
Puedes definir un *rango* para una función de Vimscript añadiendo la palabra clave `range` al final de la definición de la función. Un rango de función tiene disponibles dos variables especiales `a:firstline` y `a:lastline`.
Si estás en la línea 100 y ejecutas `call Desayuno()`, mostrará 100 tanto para `firstline` como para `lastline`. Si visualmente resaltas (mediante `v`, `V`, o `Ctrl-V`) las líneas de la 101 a la 105 y ejecutas `call Desayuno()`, `firstline` mostrará 101 y `lastline` mostrará 105. `firstline` y `lastline` muestran el rango mínimo y máximo de la función que es llamada.
Te podrías preguntar, "Está bien que una función de Vimscript acepte un rango, pero ¿no puedo obtener el número de línea con `line(".")`? ¿No hará eso lo mismo?"
Al llamar a la función `:11,20call Desayuno()` ejecuta la función `Desayuno` 10 veces (una por cada línea del rango). Compara eso con la diferencia de si hubieras pasado el argumento del rango `range`:
Si le añades la palabra clave `range` keyword y le pasas un rango numérico (como `11,20`) en la llamada `call`, Vim solo ejecutará esa función una vez. Si no pasas un rango `range` y le pasas un rango numérico (como `11,20`) en la llamada `call`, Vim ejecuta esa función un número N de veces deppendiendo del rango (en el caso del ejemplo, N = 10).
Con la palabra clave `dict`, la variable clave `self` se refiere al diccionario donde la función está almacenada (en este caso, el diccionario `comidas`). La expresión `self.desayuno` es equivalente a `comidas.desayuno`.
Una manera alternativa para añadir una función a un objeto de diccionario es utilizar *namespace*. *namespace* de forma resumida, es una manera de organizar variables o funciones para evitar conflictos de nombres. Como por ejemplo:
La expresión `function("SegundoDesayuno")` anterior es un ejemplo de *funcref*. Vim tiene una función propia `function()` que devuelve un *funcref* cuando le pasas el nombre de una función (cadena).
En Vim, si quieres asignar una función a una variable, no puedes ejecutar la asignación simplemente de esta manera `let MiVariable = MiFuncion`. Necesutas utilizar la función `function()`, así `let MiVariable = function("MiFuncion")`.
Puedes utilizar *funcref* con mapas y filtros. Ten en cuenta que los mapas y los filtros pasarán un índice como primer argumento y repetirán el valor como segundo argumento.
Una manera más adecuada de utilizar funciones en mapas y filtros es utilizando la expresión *lambda* (a veces también conocida como la función sin nombre). Por ejemplo:
Puedes encadenar varias funciones de Vimscript y expresiones *lambda* secuencialmente con `->`. Ten en mente que `->` debe ir seguido por un nombre de método *sin espacios en blanco*.
Veamos un ejemplo más complicado. Supongamos que necesitas convertir a mayúsculas la primera letra de cada elemento de una lista, y después ordenar la lista y después unir la lista para formar una cadena.
Con el método de encadenado, la secuencia es más sencilla de leer y entender. Me basta con echar un vistazo a `cena_items->ListaMayuscula()->sort()->join(", ")` y saber exactamente qué va a suceder.
Cuando defines una variable dentro de una función, esa variable exsite dentro de los límites de esa función. A esto se le llama alcance léxico (lexical scope).
`appetizer` está definido dentro de la función `Almuerzo`, que devuelve la *funcref*`SegundoAlmuerzo`. Ten en cuenta que `SegundoAlmuerzo` utiliza `appetizer`, pero en Vimscript, esta no tiene acceso a esa variable. Si intentas ejecutar `echo Lunch()()`, Vim mostrará un error de variable no definida.
Now if you run `echo Lunch()()`, Vim will return "shrimp".
## Learn Vimscript Functions The Smart Way
In this chapter, you learned the anatomy of Vim function. You learned how to use different special keywords `range`, `dict`, and `closure` to modify function behavior. You also learned how to use lambda and to chain multiple functions together. Functions are important tools for creating complex abstractions.
This concludes this Vim guide. However, your Vim journey doesn't end here. In fact, it actually starts now. You should have sufficient knowledge to go on your own or even create your own plugins.