For my "standard" language response: In general, I don't actually like using mathematics for this sort of discussion, because on the one hand it all seems so reasonable, and yet, on the other hand, almost nothing we do involves working in spaces as complicated as numbers. You see something similar in "operator overloading"... makes great sense for numbers, but everywhere else it turns into a pain, because we don't really work in numerical spaces.
The vast majority of the time, we're really working with types like
User -> Permissions -> PermissionType -> Maybe BillingHistory
and a type-based refactoring of "Permissions -> PermissionType" into "PermissionsWithType" isn't going to go wrong, even in looser languages. (Those aren't very Haskell-y types, but I'll keep the notation here for now.) As long as you can assume your code isn't entirely insane, and the permissions code isn't going to spontaneously guess permissions into existence when you go to check something with them, you're going to be
generally OK with this sort of type-directed refactoring.
To the extent that you can't actually depend on your code to not do things like magic permissions into existence... well... this is precisely why that's always a bad idea. Don't write code that does that.
For my Haskell response: In Haskell, when you know how to read the type system, you don't have to use a heuristic to guess whether you should slow down.. the type itself is telling you that you need to be more careful. As a reasonably skilled Haskell programmer using type-directed reasoning, you can immediately read off from "Int -> Int -> Int" that you have a more complicated function than "a -> a -> a". The latter is so "simple" there's only two reasonable implementations, whereas that's only the beginning of what your Int function may do. However, when you have "Applicative f => f (a -> b) -> f a -> f b", you actually can know what that is not doing. The polymorphism tells you you don't have to sit there and study it. It can't magic an "a" into existence, or suddenly decide to use "division" on the resulting "b", because it can't. It doesn't know how. You don't need to worry about it doing those things anymore, because it can't.
For my harmonization of the two: In principle, imperative code can almost always magic values into existence, use globals, etc. One of the reasons I can stand writing in looser langauges like Go is that I just don't do those things. In general, when I write code, if you've got a "Permission" and "PermissionCheckable", I do my best to keep it so there's only one sensible way to combine the two. So, when I'm refactoring my code, even in looser strictly-typed languages, I can still pretty much get away with the type-directed approach. (Backstopped by the unit tests I have anyhow.) It is certainly true that you can write, or be forced to deal with, code that is conceptually mismashed where this is more dangerous, but I'd suggest as you get experienced over the years that you learn to stop doing that.