So I did some code spelunking and testing and the actual limitation is not that functions are entirely blocked, instead only global functions and functions stored under some other global variable (including the var table) are blocked.
e.g.
function hello()
game.print('hello')
end
will not work. Likewise
var.hello = function()
game.print('hello')
end
will fail. However:
local function hello()
game.print('hello')
end
will work fine.
But!!! Important caveat which screwed up my initial testing of functions: once you have polluted the global variables for a given moon logic combinator you need to clean them up. Any global variable in the combinator is persisted between runs, just like anything in var. So even if you delete your global function declaration (and replace it with a local one as I did) your combinator will be corrupt until you either delete the combinator, or set that global variable to nil.
So for the first example you could run
hello = nil
For the second example try:
var = []
or
var.hello = nil
IIRC prior to factorio 2.0 if you tried to save functions in the var table they would get set to nil on a save/reload cycle unless you set them on every run. For global functions in your MLC code you were typically setting them on every run anyway so I never noticed that they had the same limitation. I think the only difference in 2.0 is that the game will throw an error now. (See https://forums.factorio.com/113852 )
I guess another workaround would be to put some code at the bottom of your combinator code setting each of your global functions to nil after you are done using them, that's basically what was happening before 2.0. But using locals is probably easier.
I'd kind of like to submit a PR to change moon logic 2 to set functions to nil but without an on_save hook that would be inefficient I think, would have to iterate over all of the function's globals on every tick where it does something? Maybe that's not too horrible because the amount of work scales with the complexity of the code you are using...