Hi Jozef and welcome to the list!
Indeed, relational algebra does have something to say about this scenario.
Firstly, it is worth noting that determining a key, especially a
naturally-occurring, or "natural key", is often challenging. In this
case, a name makes a poor choice for a key because it is often easy to
find two people with the same name and, as you noted, names can change.
Barring the above objection, however, the relational algebra provides
for changing key values. However, the relational model cannot
distinguish between whether a key is changed because the object being
modeled has changed or because the object was removed and a new one added.
Why? It is due to relational assignment:
people := relation{tuple{name "Steve"}}
If I wish to append a user to the relation, I can use assignment:
people := people union relation {tuple{name "Bob"}}
This is equivalent to insert. An update of "Steve" to "Stephen" is
equivalent to:
people := (people where not (name = "Steve")) union (((people where
name="Steve") : {name2:="Stephen"}){name2} rename {name2 as name})
Given this assignment scheme, we see that the relations are indeed
immutable and we can follow that there is no way for the model to
extract additional meaning from the update. That's why SQL database
include additional schemes like "ON UPDATE CASCADE/DELETE". The
underlying model simply cannot know so let's the user choose.
The sample scala implementation is effectively identical to what the
answer by Yawar. In scala, we create references which are necessarily
unique to track the users, then the names are no longer keys. Similarly,
in Haskell, artificial unique identifiers are created alongside the
names. Note that the artificial/unnatural keys have no relationship to
the people and effectively model nothing in the real world. They are
"synthetic keys".
With regards to:
"..if I want to model a normalized SQL database in Haskell is there a
general way to describe foreign keys? By general I mean, not hand coding
the IDs as suggested by chepner in the comments below."
Project:M36 is not an SQL database, but it does otherwise fit your
criteria. The project uses immutable structure to represent the database
state which is typed as:
DatabaseContextExpr (insert, update, assign, etc.) -> TransactionGraph
-> TransactionGraph
The reason that the ProjectM36.Client module requires the IO monad is
because the database can be remote, but a pure, in-memory client would
also be possible. Project:M36 integrates very with Haskell and allows
for ADTs as database values- something which SQL databases will never do.
However, nothing about Project:M36 prevents one from making a relation
variable with a poor choice of keys.
Feel free to try out the examples above at
https://try.project-m36.io/
Cheers,
M