Yes, it is indeed possible, at least in some cases.
Basically, you need a way to efficiently store the lazy operation, and a way to efficiently combine two stored lazy operations into one.
For instance, say the update operation is segment assignment, that is, a[l] = x, a[l+1] = x, ..., a[r-1] = x, a[r] = x.
This operation on the whole subtree can be stored as just the value x, which will mean that the operation was to assign x to every vertex of this subtree.
For lazy propagation in vertex v, we just apply it to immediate children of vertex v, and store the same lazy operation x there.
Note that any old lazy operation in children is just erased by the assignment.
Such is the nature of assignment.
As for your added example, operation arr[i] = (1 - (1 - arr[i]) * a), let us see how the value changes after two such operations with constants a and b.
Before operations, let the value be v.
After the first one, it becomes w = 1 - (1 - v) * a, which is a*v + (1-a)*1.
After the second operation, the value becomes 1 - (1 - w) * b, which is b*w + (1-b)*1, which in turn is b*a*v + b*(1-a)*1 + (1-b)*1, and finally becomes (b*a)*v + (1-b*a)*1.
(I may have mixed up +s and -s, but that hopefully does not change the whole picture.)
We can now see that the value is a linear function of the original value, so we can store the coefficients b*a and 1-b*a of the linear and constant terms, respectively.
The problem now is that the coefficients may grow too quickly, and will soon exceed the capacity of the storage type (int, double or whatever).
Now, if the problem deals with integer residues modulo some integer instead of just integers or reals, that's not an issue; otherwise, storing the coefficients soon becomes problematic.