Okay, I've actually found my own solution.
First I deleted the role assignments so I could take a different approach. My approach was not to use the newGuid() function as the default value of x parameters in the template. This was causing a new GUID, and therefore a new "name" property value to be generated every time the pipeline was run, and therefore the role assignment was different each time it was compared, but the essential components - Principal, Role, and Scope were the same.
My objective was to ensure idempotency of the GUID, and so I used the guid() function instead, passing unique arguments for each roleAssignment resource which would not change across runs of the pipeline.
Here's a snippet:
{
"type": "Microsoft.Authorization/roleAssignments",
"apiVersion": "2020-04-01-preview",
"name": "[guid(resourceGroup().name, parameters('myArrayOfFunctionAppNames')[0])]",
"scope": "[concat('Microsoft.Storage/storageAccounts/', parameters('storageAccName'), '/tableServices/default/tables/', parameters('tableName'))]",
"dependsOn": [
"[parameters('storageAccName')]"
],
"properties": {
"roleDefinitionId": "[variables('builtInRoles')['storageAccTableDataContributor']]",
"principalId": "[reference(resourceId(subscription().subscriptionId, parameters('myResourceGroupName'), 'Microsoft.Web/sites', parameters('myArrayOfFunctionAppNames')[0]), '2019-08-01', 'Full').identity.principalId]"
}
}
The above is grabbing the principalId of the Function App in question using the reference() function, and my Function App names are stored in a parameter of type array which is why I reference the index [0] in this case, but increment for other roleAssignment resources to ensure uniqueness of GUID but idempotence across pipeline runs.
Et voila.