Sunday, November 11, 2018

Refactoring Towards Language

When implementing MVC in iOS and macOS, one common pattern is to use NSNotification to implement the notification that the model has changed and the view should redraw itself. This is a good pattern, as that is how MVC is intended to work, but it does lead to quite a bit of boilerplate in this incarnation.

Specifically, you typically have some version of the following code in one of the initialisation methods of your ViewControllers, which play the MVC role of the View in Cocoa and Cocoa Touch.


- (void)viewDidLoad
{
	...
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(modelDidChange:)
                                                 name:MPWAppNotificationModelDidChange
                                               object:nil];
	...
}

Refactor I

That's all good, except that you tend to repeat that code in every single ViewController, and there are usually quite a few of them. So one way of avoiding all that duplication is to refactor by extracting the duplicated functionality into a method.
- (void)subscribeToModelDidChangeNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(modelDidChange:)
                                                 name:MPWAppNotificationModelDidChange
                                               object:nil];
}


- (void)unsubscribeFromModelDidChangeNotification
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:MPWAppNotificationModelDidChange
                                                  object:nil];
}

- (void)subscribeToModelWasDeletedNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(modelDidChange:)
                                                 name:MPWAppNotificationModelWasDeleted
                                               object:nil];
}


- (void)unsubscribeFromModelWasDeletedNotification
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:MPWAppNotificationModelWasDeleted
                                                  object:nil];
}
...

Refactor II

The interesting thing that happens when you remove duplication is that it will often reveal more duplication. In particular, if you look at more of these, you will notice that the method bodies are largely identical. You always send a message to the defaultCenter of the NSNotificationCenter class, you pretty much always add yourself as the Observer, and I've rarely seen the object: parameter used. The only things that vary are the selector to send and the name of the notification.
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector: <some selector>
                                                 name: <some notification>
                                               object:nil];


So yet another classic case for refactoring by extracting a helper method that encapsulates the things that do not change and takes the things that do vary as arguments.
-(void)subscribeToNotification:(NSString *)notificationName usingSelector:(SEL)selectorName
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:selectorName
                                                 name:notificationName
                                               object:nil];
}

We can also apply the same refactoring to unsubscribing.
-(void)unsubscribeFromNotification:(NSString *)notificationName
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:notificationName
                                                  object:nil];
}

With the parameters extracted, our helper methods simplify to the following.
- (void)subscribeToModelDidChangeNotification
{
    [self subscribeToNotification: MPWAppNotificationModelDidChange usingSelector:@selector(modelDidChange:)];
}

- (void)unsubscribeFromModelDidChangeNotification
{
    [self unsubscribeFromNotification: MPWAppNotificationModelDidChange];
}

- (void)subscribeToModelWasDeletedNotification
{
    [self subscribeToNotification: MPWAppNotificationModelWasDeleted usingSelector:@selector(modelDidChange:)];
}

- (void)unsubscribeFromModelWasDeletedNotification
{
    [self unsubscribeFromNotification: MPWAppNotificationModelWasDeleted];
}

Refactor III

As happens very often when you extract code and remove duplication we discover more duplication that was previously somewhat hidden, for example because code that was too far away to really notice the duplication is now close enough together to make it more obvious. Specifically: we always define the same methods: subscribeTo<notification> and unsubscribeFrom<notification>. We also repeat the notification name in each of the method names, and also in each of the method bodies (with different prefixes/suffixes).
- (void)subscribeTo<notification-name>Notification
{
    [self subscribeToNotification: MPWAppNotification<notification-name> usingSelector:@selector(<notification-selector>:)];
}

- (void)unsubscribeFrom<notification-name>Notification
{
    [self unsubscribeFromNotification: MPWAppNotification<notification-name>];
}
- (void)<notification-selector>:(NSNotification *)notification
{
}

Up to this point, we managed to achieve our goals using plain refactoring techniques. Often, that is enough, and it’s generally a good idea to stop there, because beyond lies metaprogramming, which tends to make the code significantly more obscure. In Objective-C, we generally have runtime tricks and macro-programming using the C pre-processor (well: and code generation). In this case, I decided to give the C preprocessor a try, because I wanted things done at compile-time and the problem was parametrising parts of identifiers, i.e. string processing.

Doing some pattern matching, we have two actual “parameters", the “base” of the notification name ( for example ‘ModelDidChange’ or ‘ModelWasDeleted’ ) and the name of the method to call. So we create a Macro that takes these parameters and applies them to a template of the methods we need:


#define SUBSCRIBE_UNSUBSCRIBE_WITHMETHODNAME( name , notification, theMessage ) \
- (void)subscribeTo##name \
{\
    [self subscribeToNotification:notification usingSelector:@selector(theMessage:)];\
}\
\
- (void)unsubscribeFrom##name \
{\
    [self unsubscribeFromNotification:notification];\
}\
-(void)theMessage:(NSNotification *)notification\
{\
}\

#define SUBSCRIBE_UNSUBSCRIBE( commonName, theMessage ) SUBSCRIBE_UNSUBSCRIBE_WITHMETHODNAME( commonName##Notification, MPWAppNotification##commonName , theMessage )

The first macro has three arguments, a version of the notification name suitable for adding to the method names, the full notification name and the message name. The ‘##’ is the token pasting operator, which allows us to create new lexical tokens out of existing ones. We “need” it in this case because we have commonality we want to express that is on a sub-token, sub-identifier level.

There are a bunch of issues with this approach. First, it is pretty unreadable. You need to terminate every line with backslashes because Macro definitions can only be on a single logical line, the backslashes continue the logical line over physical lines. Dealing with compiler errors and warnings is rather tricky, because the error will be with the result of the macro expansion, which the error message will usually not contain. (You can tell the compiler to run just the pre-processor and give you the result, in case you need to debug and can’t do the macro expansion in your head…). Finally, you cannot search for the tokens that get generated by the pre-processor. So for example no command-clicking, and you have to be careful if you ever change one of the notification names.

The advantage is that you get a representation that succinctly states what you want to accomplish, without any duplicated boilerplate. No duplication also means no chance to get those duplicates wrong: the first few code samples in this post actually contain an error. (MPWAppNotificationModelWasDeleted also sends the modelDidChange: message instead of its own modelWasDeleted:) This error, almost certainly a copy-and-paste bug, was actually in the original code and had gone unnoticed for months until I tried to remove that duplication.

I don't know about you, but my eyes just glaze over when scanning large swathes of mostly duplicated code.

Anyway, with the Macros, our help methods are now defined as follows:


SUBSCRIBE_UNSUBSCRIBE( ModelDidChange, modelDidChange )
SUBSCRIBE_UNSUBSCRIBE( ModelWasDeleted, modelWasDeleted )

SUBSCRIBE_UNSUBSCRIBE( ActivityCountDidChange, activityCountDidChange )
SUBSCRIBE_UNSUBSCRIBE( ObjectIdDidChange, objectIdDidChange )
SUBSCRIBE_UNSUBSCRIBE( UserDidLogin, userDidLogin )

Note that previously we showed just two notifications, now we are showing all five, which would have been quite unwieldy before.

Refactor IV

As is typical when you remove duplication, you notice more duplication, because stuff is closer together: the two parameters are almost identical, except for capitalisation. This doesn't have to be the case, but it is a good convention to have. Alas, the pre-processor can’t change the capitalisation of strings so we are stuck.

To recap, this was a process of detecting duplication, removing that duplication using available mechanisms and then detecting more duplication, over several iterations. What then usually happens is that you either manage to remove all the duplication, or you notice that you cannot reduce duplication any further. Not being able to remove duplication typically means that you have reached limitations of your language, with metaprogramming facilities allowing you to push those limitations at least a little, and sometimes quite a bit.

This particular exploration relied on a somewhat formulaic use of NSNotificationCenter, one that always matches a notification with the same message. Since messages are already late-bound, this doesn't really present much of a restriction and seems a useful simplification, and it is a simplification that I've seen used widely in practice. The other pattern is that specific classes typically observe notifications for essentially their entire lifetime, meaning that the ability to dynamically turn notifications on and off is often not needed.

If we imagine language support for notifications (the implicit invocation architectural style), we would probably like to be able to declare notifications, declare that a class listens to a specific notification and and check conformance. This would make usage even more convenient than the macros we refactored to, while at the same time addressing the problems of the macro solution.

And of course that is what Notification Protocols became: a slight misappropriation of Objective-C and Swift Protocols to get something that is extremely close to actual language support. I can now actually declare my notifications as almost a programming-language thing (at least better than a string), and also specify the relationship between a message and that notification, which otherwise is purely lost in convention:


@protocol ModelDidChange <MPWNotificationProtocol>

-(void)modelDidChange:(NSNotifiction*)notification;

@end

I can also easily and declaratively specify that a particular class listens to a notification:
@interface NotifiedView:NSView <ModelDidChange>

@end

Not only does this remove the problems with the Macro approach, unreadability and untraceability, it is actually quite a bit better than the approach without Macros, all while being at least as compact. Last not least, notification sending is not just compact, but also obvious and at least somewhat compiler-checked:
[@protocol(ModelDidChange) notify];

This is very, very close to native language support, due to some lucky coincidences and the wise decision to make Protocols first class objects in Objective-C. It also does not match the flexibility of the NSNotificationCenter APIs, but I doubt whether that additional flexibility is actually used/useful.

Had I not experimented with removing duplication, and iterated on removing duplication, I never would have come to the point where notification protocols became an obvious solution. And now that I have notification protocols, I also have a good idea what actual language support should look like, because I've been using something pretty close.

So for me, plain old refactoring, different kinds of metaprogramming and language support all live on a continuum of improving expressiveness, and all are, or at least should be part of our feedback loops. How can we improve our language so we don't need metaprogramming for common use cases? How can we improve our metaprogramming facilities so they are sufficient and we don't feel a need for replacing them with actual language support? How can we do both, turn things that currently require metaprogramming look more like plain base-level programming while making it integrate better than metaprogramming solutions?

That, Detective, is the right question. Program terminated

Wednesday, October 31, 2018

Even Easier Notification Sending with Notification Protocols in Objective-C

Having revisited Notification Protocols in order to ensure they work with Swift, I had another look at how to make them even more natural in Objective-C.

Fortunately, I found a way:


[@protocol(ModelDidChange) notify:@"Payload"];
This will send the ModelDidChange notification with the object parameter @"Payload". Note that the compiler will check that there is a Protocol called ModelDidChange and the runtime will verify that it is an actual notification protocol, raising an exception if that is not true. You can also omit the parameter:
[@protocol(ModelDidChange) notify];
In both cases, the amount of boilerplate or semantic noise is minimised, whereas the core of what is happening is put at the forefront. Compare this to the traditional way:
[[NSNotificationCenter defaultCenter] postNotificationName:@"ModelDidChange" object:@"Payload"]
Here, almost the entire expression is noise, with the relevant pieces being buried near the end of the expression as parameters. No checking is (or can be) performed to ensure that the argument string actually refers to a notification name.

Of course, you can replace that literal string with a string constant, but that constant is also not checked, and since it lives in a global namespace with all other identifiers, it needs quite a bit of prefixing to disambiguate:


[[NSNotificationCenter defaultCenter] postNotificationName:WLCoreNotificationModelDidChange object:@"Payload"]
Would it be easy to spot that this was supposed to be WLCoreNotificationModelWasDeleted?

The Macro PROTOCOL_NOTIFY() is removed, whereas the sendProtocolNotification() function is retained for Swift compatibility.

Notification Protocols from Swift

When I introduced Notification Protocols, I mentioned that they should be Usable from Swift.

This is code from a sample Swift Playground that shows how to do this. The Playground needs to have access to MPWFoundation, for example by being inside a Xcode workspace that includes it.


import Foundation
import MPWFoundation

@objc protocol ModelDidChange:MPWNotificationProtocol {
    func modelDidChange( payload:NSNotification );
}

class MyView : NSObject,ModelDidChange {
    override public init() {
        super.init()
        self.installProtocolNotifications()
    }
    func modelDidChange( payload:NSNotification ) {
        print("I was notified, self: \(self) payload: \"\(payload.object!)\"")
    }
}

let target1 = MyView()
let target2 = MyView()

sendProtocolNotification( ModelDidChange.self , "The Payload")

A brief walkthrough:
  1. We declare a ModelDidChange notification protocol.
  2. We indicate that it is a notification protocol by adopting MPWNotificationProtocol.
  3. The notification protocol has the message modelDidChange
  4. We declare that MyView conforms to ModelDidChange. This means we declaratively indicate that we receive ModelDidChange notifications, which will result in MyView instance being sent modelDidChange() messages.
  5. It also means that we have to implement modelDidChange(), which will be checked by the compiler.
  6. We need to call installProtocolNotifications() in order to activate the declared relationships.
  7. We use sendProtocolNotification() with the Protocol object as the argument and a payload.
  8. The fact that we need a protocol object instead of any old String gives us additional checking.
Enjoy!

Tuesday, July 3, 2018

A one word change to the C standard to make undefined behavior sane again

A lot has been written on C undefined behavior, some of it by myself and a lot more by people who know a lot more about compilers than I do. However, I now believe that a seemingly innocuous but far-reaching change to the standard has given permission for the current craziness, and I think undoing that change could be a start in rectifying the situation.

Proposal

In section 3.4.3, change the word "possible" back to "permissible", the way it was in C89.

Background

In all versions of the standard I have checked, section 3.4.3 defines the term "undefined behavior".
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

So that seems pretty clear, the compiler can do whatever it wants. But wait, there is a second paragraph that clarifies:

Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
So it's not a free-for-all, in fact it is pretty clear about what the compiler is and is not allowed to do, as there are essentially three options:
  1. It "ignores" the situation completely, so if the CPU hardware produces an overflow or underflow on an arithmetic operation, well that's what you get. If you write to a string constant, the compiler emits the write and either the string constant might get changed if there is no memory protection for string constants or you might get a segfault if there is.
  2. It "behaves in a manner characteristic of the environment". So no "demons flying out of your nose" nonsense, and no arbitrary transformations of programs. And whatever you do, you have to document it, though you are not required to print a diagnostic.
  3. It can terminate with an error message.
I would suggest that current behavior is not one of these three, and it's not in the range bounded by these three either. It is clearly outside that defined range of "permissible" undefined behavior.

But of course compiler writers have an out, because more recent versions of the standard changed the word "permissible", which clearly restricts what you are allowed to do, to "possible", which means this is just an illustration of what might happen.

So let's change the word back to "permissible".

Saturday, April 21, 2018

Even Simpler Notifications: Notification Messages

The Notification Protocols implementation I showed yesterday is actually just one in a series of implementations that came out of thinking about notifications.

The basic insight, and it is a pretty trivial one, is that a notification is just a broadcast message. So when we have real messaging, which we do, notifications consist of sending a message to a dynamically defined set of receivers.

How can we send a message, for example -modelDidChange: to a set of receivers? With HOM that's also trivial:


[[receivers do] modelDidChange:aPath];

So how do we define the set of receivers? Well, the collection is defined as all the objects listening to a particular notification, in this case this notification is the message. So instead of do, we need a notify: HOM that collects all the receivers registered for that message:


[[center notifyListenersOf:@selector(modelDidChange:)] modelDidChange:aPath];

But of course this is redundant, we already have the modelDidChange: message in the argument message of the HOM. So we can remove the argument from the notify HOM:


[[center notify] modelDidChange:aPath];

A trivial notification center now is just a dictionary of arrays of receivers, with the keys of the dictionary being the message names. With that dictionary called receiversByNotificationName, the notify HOM would look as follows:
DEFINE_HOM(notify, void)
{
    NSString *key = NSStringFromSelector([invocation selector]);
    NSArray *receivers = self.receiversByNotificationName[key];
    [[invocation do] invokeWithTarget:[receivers each]];
}

Of course, you can also integrate with NSNotificationCenter by using the converted message name as the NSNotification name.

You would then register for the notification with the following code:


-(void)registerNotificationMessage:(SEL)aMessage
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:aMessage name:NSStringFromSelector(aMessage) object:nil];
}

The HOM code is a little more involved, I'll leave that as an exercise for the reader.

So, by taking messaging seriously, we can get elegant notifications in even less code than Notification Protocols. The difference is that Notification Protocols actually let you declare adoption of a notification statically in your class's interface, and have that declaration be meaningful.


@interface MyGreatView : NSView <ModelDidChangeNotification>

Another aspect is that while HOM is almost certainly too much for poor old Swift to handle, it should be capable of dealing with Notification Protocols. It is!

Friday, April 20, 2018

Notification Protocols

NSNotificationCenter is straightforward mechanism for implementing a clean MVC architecture, but it is also somewhat cumbersome and error-prone. The receiving object has to register itself with the notification center for the particular notification, specified by a string. It also has to implement a method for handling the notification, and the registration and the method have to match. The string used has to be coordinated with the senders of the notification, with no checking whatsoever. The compiler doesn't help with the manual bookkeeping and there is no indication in the interface that the class receives notifications.

Let's fix this with Notification Protocols.

Notification Protocols: Usage

First, let's define a protocol for our notification. We want a ModelDidChange notification. We also want common message to be sent to our objects, in this case the message -modelDidChange:.
@protocol ModelDidChange <MPWNotificationProtocol>

-(void)modelDidChange:(NSNotifiction*)notification;

@end

This protocol must contain a single message and conform to MPWNotificationProtocol, which indicates that this is a notification protocol. This way, we define that a particular notification always maps to one specific message, but this is common and should probably be considered a best-practice.

Second, a class wishing to adopt this notification must conform to the protocol:


@interface NotifiedView:NSView <ModelDidChange>

@end

NotifiedView must implement -(void)modelDidChange:(NSNotificaiton*)notification;, but fortunately the compiler will tell us if we forget to do this, because of the protocol conformance declaration. Finally, and this is the part that can't really be checked, the object must call [self installProtocolNotifications] somewhere in its initializer. It's probably best to do this in a common superclass.

Then, just use the PROTOCOL_NOTIFY macro to send the notification. The macro takes the name of the protocol as its argument. It uses the @protocol compiler directive to turn that into the protocol and then uses the protocol's name for the notification.


PROTOCOL_NOTIFY(ModelDidChange,changedUri);

That's it! Your NotifiedView will now get the -modelDidChange: message. The string is hidden behind the use of @protocol, meaning the compiler helps a bit in checking that we are sending the right notification (there are far fewer protocols than strings). The protocol also helps us keep the mapping from notification to message straight, mostly by automating it away. It also allows us to declare conformance to the notification statically, both for readers and for checking that we actually implement the method.

Notification Protocols: Implementation

The implementation is quite simple: the -installProtocolNotifications method iterates over all the protocols an object's class conforms to. For the ones that conform to MPWNotificationProtocol it registers itself with NSotificationCenter to be sent the message in the protocol.

The implementation is part of MPWFoundation.

Monday, August 7, 2017

The Science Behind the "Google Manifesto"

The "Google Diversity Manifesto" has created quite a bit of controversy. One thing that hasn't helped is that (at least that's what I read), Gizmodo stripped the links to the scientific evidence supporting the basic premises. For me, being at least roughly aware of that research, the whole thing seemed patently unremarkable, to others apparently not so much:

Now I don't think everyone has to agree with what was written, but it would help to at least get to a common understanding. I didn't find anything in the text that said or even hinted that women were "inferior", but apart from the chance that I just missed it, it also seems that some of the ideas and concepts presented might at least "feel" that way when stripped of their context.

Ideally, we would get the original with citations and figures, but as a less-then-ideal stopgap, here are some references to the science that I found.

UPDATE: The original document has been published.

Biases

The text starts with a list of biases that the author says are prelavent in the political left and the political right. This seems to be taken directly from Jonathan Haidt.
Text, Slides

Article in the New York Times: Forget the money follow the sacredness

Possible non-bias causes of the "gender gap"

Second, after acknowledging that biases hold people back, the author goes into possible causes of a gender gap in tech that are not bias, and may even be biological in nature. There he primarily goes into the gender differences in the Big Five personality traits.

As far as I can tell, the empirical solidity of the Big Five and findings around them are largely undisputed, the criticism listed in the Wikipedia page is mostly about it not being enough, being "just empirical". One thing to note is that terms like "neuroticism" in this context appear to be different from their everyday use. So someone with a higher "neuroticism" score is not necessarily less healthy than one with a lower score. Seeing these terms without that context appears to have stoked a significant part of the anger aimed at the paper and the author.

Jordan Peterson has a video on the same topic, and here are some papers that show cross-cultural (hinting at biological causes) and straight biologically caused gender differences in these personality traits:

So yes, there are statistical gender differences. None of these say anything about an individual, just like most physical differences: yes, men are statisticially taller than women. Yet, there are a lot of women that are taller than a lot of men. Same with the psychological traits, where the overlap is great and there is also no simple goodness scale attached to the traits.

As a matter of fact, it appears to be that one reason women choose tech less than men is that women who have high math ability also tend to have high verbal ability, whereas men with high math ability tend to have just the high math ability. So women have more options, and apparently people of either gender with options tend to avoid tech: Why Brilliant Girls Tend to Favor Non-STEM Careers

Of course, the whole idea that there are no biological reasons for cognitive differences is The Blank Slate hypothesis, which was pretty thoroughly debunked by Steven Pinker in his book of the same title: The Blank Slate. What's interesting is that he documents the same sort of witch hunt we've seen here. This is not a new phenomenom.

Even more topical, there was also the Pinker/Spelke debate "...on the research on mind, brain, and behavior that may be relevant to gender disparities in the sciences, including the studies of bias, discrimination and innate and acquired difference between the sexes."

This covers a lot of the ground alluded to in the "manifesto", with Pinker providing tons and tons of interlocking evidence for there being gender-specific traits and preferences that explain the gaps we see. Almost more interestingly, he makes a very good case that the opposite thesis makes incorrect predictions.

There is lots and lots more to this. One of my favorite accessible (and funny!) intros is the Norwegian Documentary The Gender Equality Paradox. The documentary examines why in Norway, which is consistently at the top of world-wide country rankings for gender equality, professions are much more segregated than in less egalitarian countries, not less.

Empathy

I was surprised to find this, but what he writes about is exactly the thesis of Paul Bloom's recent book Against Empathy. (amazon, goodreads, New York Times).
Brilliantly argued, urgent and humane, AGAINST EMPATHY shows us that, when it comes to both major policy decisions and the choices we make in our everyday lives, limiting our impulse toward empathy is often the most compassionate choice we can make.
One small example he gives is that empathy tends to make us give much weight to an individual being harmed than many people being harmed, which is a somewhat absurd outcome when you think about it. There's a lot more, it's a fascinating read that forces you to think and question some sacred beliefs.

Microagressions

Microaggressions: Strong Claims, Inadequate Evidence:
I argue that the microaggression research program (MRP) rests on five core premises, namely, that microaggressions (1) are operationalized with sufficient clarity and consensus to afford rigorous scientific investigation; (2) are interpreted negatively by most or all minority group members; (3) reflect implicitly prejudicial and implicitly aggressive motives; (4) can be validly assessed using only respondents’ subjective reports; and (5) exert an adverse impact on recipients’ mental health. A review of the literature reveals negligible support for all five suppositions.

The Science of Microaggressions: It’s Complicated:

Subtle bigotry can be harmful, but research on the concept so far raises more questions than answers.
[..]
Still, the microaggression concept is so nebulously defined that virtually any statement or action that might offend someone could fall within its capacious borders.
[..]
The science aside, it is crucial to ask whether conceptualizing the interpersonal world in terms of microaggressions does more good than harm. The answer is “We don’t know.” Still, there are reasons for concern. Encouraging individuals to be on the lookout for subtle, in some cases barely discernible, signs of prejudice in others puts just about everyone on the defensive. Minority individuals are likely to become chronically vigilant to minor indications of potential psychological harm whereas majority individuals are likely to feel a need to walk on eggshells, closely monitoring their every word and action to avoid offending others. As a consequence, microaggression training may merely ramp up already simmering racial tensions.

Conclusion

I hope this gives a bit of background and that I haven't mis-represented the author's intent.