Deep Generic Specialization

While I can see that that is your expected usage, that's not something that the compiler would just know, and we're talking about compiler-wide setting here. It's utility is very limited beyond this specific scenario. If you'd want to specialize a very specific generic, top-level erasedApply in this case, you normally would just use @_specialize.

Also, I doubt we (you & me) would actually be implementing this new compiler features. So the point is moot anyway.

Just to confirm, this is release build, correct?

I mean, sure, but I don't see why ErasedReducer would be used on anything but a top-level reducer, which are not necessarily the same as non-top-level reducers type-wise.

PS

Also, I'm not sure we mean the same thing when referring to static dispatch since is has a very specific meaning in Swift. Instance members use

  • static dispatch if it uses the same underlying function regardless of the type of the instance, while
  • dynamic dispatch if the actual function being called may change depending on the type of the instance.

In so far, using that definition, class functions and protocol requirements use dynamic dispatch.

1 Like

Well, but the compile should be able to figure out which parts of a codebase can actually be reached by an executable, right? Sure, it's the developer's responsibility to have only one reducer per app and it's the framework developer's responsibility to point out how having multiple reducers might lead to an explosion of binary size, but the compiler should be smart enough to figure out "this reducer type is actually instantiated, these actions are actually called, so here's the list of specializations that I actually need to make." Valid only for static libs of course.

Fair.

Yep. I tried -O and -whole-module-optimization.

Hmmm, which protocol would ComposedReducer implement then?

In the context of unidirectional dataflow architectures, one can think of each action that you send to the reducer as one method. There is really a 1:1 correspondence between actions sent to a global reducer and methods on your global state object that are only allowed to mutate that state and that don't have any other side-effects. There are only two advantages of unidirectional data-flow architectures: 1. some degree of enforcement of pureness. 2. that you can apply (effectful) decorators to all your methods at once.

The way how in traditional unidirectional dataflow architectures an action finds its way to the reducer that responds to it is essentially by pattern matching. You have to figure out which enum case you actually have, and unless there is an optimization that I'm not aware of, pattern matching happens strictly at runtime - it's a dynamic feature.

Generics on the other hand could potentially be specialized and optimized away - it's at least conceptually a static feature.


Ok, now that I'm thinking of it, I could actually maybe use church encoding... church encoding of AGTs can bring down pattern matching to O(1) in wide AGTs (just look up and call the concrete type's definition of the match method in O(1)), and O(depth) in trees. That doesn't sound too bad actually... Not exactly O(1) method lookup, but really not too bad. Still a bit of work to do to figure out how to make that convenient for users, but it might be worth a shot.

Nevermind.

Apparently, enums totally achieve the efficiency I was looking for and this conversation came out of a bad assumption on my part. At least, I‘ve learned something, and maybe my design approach will still help me to define a more convenient API when I migrate to something that does store concrete actions.