Why do functions created with apoc.custom.declareFunction (or asFunction) behave differently when returning a MAP value vs other types?

With apoc 4.4, when I try to declare a function that modifies a MAP value, the returned MAP is wrapped inside another map with the key of the outer map being the name of the last expression. For example:

CALL apoc.custom.declareFunction('answer(map :: MAP) :: MAP','RETURN $map as answer', false)

...and then calling it:

RETURN custom.answer({foo: 1}) as row

... returns:

{ "answer": { "foo": 1 } }

I would expect it to return this instead:

{"foo": 1 }

Is there something special about MAP's that I should be aware of or what's going on here?

BTW, I found this Github issue that seems related, but they don't really explain the "why" and it looks like the fix only addressed Node/Relationship types, not raw maps.

@hindog

it is wanted because we want to "emulate" the behavior of the custom procedures via function.
For example, if I have this procedure

CALL apoc.custom.declareProcedure('testProc() :: (one :: INT, two :: INT)','RETURN 1 as one, 2 as two')

I can execute:

call custom.testProc

results:
`one` | `two`
1	| 2

To do something like that with function I have to write:

CALL apoc.custom.declareFunction('testFun() :: MAP','RETURN 1 as one, 2 as two')

so:

return custom.testFun() as res

results: ---
`res`

{
  "one": 1,
  "two": 2
}

but indeed, if I want to return only the map, like your query, I have to do something weird, for example:

CALL apoc.custom.declareFunction('funWithMap(map :: MAP) :: STRING' , 'RETURN {a: 1}' )

and this is of course wrong because of string as a return type.

I think a solution would be, not to break change, to add an option to "force" the MAP result without wrap the result.

I added a GitHub issue to track it.