It's 11000 lines (for 2.5 million lines of code or so) but it's really a long list of things to do. Functions to look for, libraries to use, configuration symbols to associate with a specific file.
The only somewhat repetitive idiom is
foo = dependency('name', method: 'pkg-config', required: get_option('opt').disable_auto_if(condition))
to find a library, but otherwise there's very very little repetition of the kind that can be fixed with user-defined functions.
That's not surprising though. Because the user input is very constrained, I find it more natural to think of the build as "first prepare all these things, then use them to prepare all these other things" and so on, not as a complex program. As a very simple example, instead of abstracting "find flags for the C/C++ compiler" in a function and calling it twice, I prepare an array of languages and iterate on it.
It seems limiting but really it's just different and this kind of data flow mindset makes it very clear what is happening when (e.g. what files or arrays are read and which are written), and it also does not need a lot of abstractions (functions).
What it needs is decent data structures, and Meson's DSL is superior to CMake in that respect. So basically the code you write matches the tools you have.
Now I am not saying that Meson is perfect. But the functions thing is kind of a red herring. My #1 complaint is lack of include files (apart from going down to a subdirectory), not functions. #2 is messy handling of targets that generate files in subdirectories and #3 is a silly handling of some options.
The important thing is that none of these things, and not even functions for that matter, are baked in the language. There is room for fixing them in the future, because they are missing features or bugs rather than fundamental mismatches.
To be honest I believe that Meson should have used Starlark as the language, but it did not exist 10 years ago and anyway there is no Python implementation of it.