Composabilité et associativité avec un langage objet
Romain Berthon
#JobHacker
@RomainTrm
romainberthon.blog
Définitions
Composabilité
a
b
c
f
g
g ∘ f
f : a → b f(a) = b
g : b → c g(b) = c
g ∘ f : a → c g(f(a)) = c
Exemple (addition) : x + 3 + 4 = x + 7
Composabilité
f : int → int f(a) = b
g : int → string g(b) = c
g ∘ f : int → string g(f(a)) = c
Func<int, int> f = x => x + 1;
Func<int, string> g = x => x.ToString();
[Test]
public void Composability([Random(20)] int a)
{
var b = f(a);
var c = g(b); // b = a + 1
Func<int, string> gAfterF = x => g(f(x));
Assert.AreEqual(gAfterF(a), c);
}
Associativité
f : a → b
g : b → c
h : c → d
a
b
c
d
f
g
h
h ∘ (g ∘ f) = (h∘ g) ∘ f
g ∘ f
h ∘ g
Exemple (additions) : (a + b) + c = a + (b + c)
Associativité
f : int → int
g : int → string
h : string → string
h ∘ (g ∘ f) = (h∘ g) ∘ f
Func<int, int> f = x => x + 1;
Func<int, string> g = x => x.ToString();
Func<string, string> h = x => "Result " + x;
[Test]
public void Associativity([Random(20)] int a)
{
Func<int, string> gAfterF = x => g(f(x));
Func<int, string> hAfterG = x => h(g(x));
Assert.AreEqual(
h(gAfterF(a)), // h ∘ (g ∘ f)
hAfterG(f(a))); // (h ∘ g) ∘ f
}
Un cas concret...
... avec les tests unitaires
[Test]
public void TestWhereOnlyIdMatter()
{
var artistId = Guid.NewGuid();
var artist = new Artist(
artistId,
"John",
"Frusciante"
/*Other parameters*/);
// Test ...
}
Une Factory Method
public static Artist SomeArtist(Guid artistId)
{
return new Artist(
artistId,
"John",
"Frusciante"
/*Other parameters*/);
}
[Test]
public void TestWhereOnlyIdMatter()
{
var artistId = Guid.NewGuid(); var artist = SomeArtist(artistId);
// Test ...
}
Beaucoup de Factory Method...
static Artist SomeArtist(
Guid artistId)
static Artist SomeArtist(
string firstName,
string lastName)
static Artist SomeArtist(
Guid artistId,
string band)
static Artist SomeArtist(
...)
// ...
// You got it !
Un Builder
Un Builder simple
public class ArtistBuilder
{
public Guid ArtistId { get; set; }
= Guid.NewGuid();
public string FirstName { get; set; }
= "Thom";
public string LastName { get; set; }
= "Yorke";
// Other properties ...
public Artist BuildArtist()
=> new Artist(ArtistId, FirstName, LastName /*...*/);
}
Un Builder simple
[Test]
public void TestWhereOnlyIdMatter()
{
var artistId = Guid.NewGuid();
var artist = new ArtistBuilder
{
ArtistId = artistId
}
.BuildArtist();
// Test ...
}
Un Builder plus avancé
public class ArtistBuilder
{ private Guid _artistId = Guid.NewGuid();
private string _firstName = "Leïla";
private string _lastName = "Huissoud";
// Other properties ...
private ArtistBuilder() { }
public static ArtistBuilder AnArtist() => new ArtistBuilder();
public void WithId(Guid artistId)
{
_artistId = artistId;
// Add needed behavior here ...
}
// Other methods ...
public Artist BuildArtist()
=> new Artist(_artistId, _firstName, _lastName /*...*/);
}
Un Builder plus avancé
[Test]
public void TestWhereOnlyIdMatter()
{
var artistId = Guid.NewGuid();
var builder = ArtistBuilder.AnArtist();
builder.WithId(artistId);
var artist = builder.BuildArtist();
// Test ...
}
Un Builder composable & associatif
public class ArtistBuilder
{
private Guid _artistId = Guid.NewGuid();
private string _firstName = "Beth";
private string _lastName = "Ditto";
// Other fields ...
private ArtistBuilder() { }
public static ArtistBuilder AnArtist() => new ArtistBuilder();
public ArtistBuilder WithId(Guid artistId)
{
_artistId = artistId;
return this;
}
// Other methods ...
public Artist BuildArtist()
=> new Artist(_artistId, _firstName, _lastName /*...*/);
}
Un Builder composable & associatif
[Test]
public void TestWhereOnlyIdMatter()
{
var artistId = Guid.NewGuid(); var artist = ArtistBuilder
.AnArtist()
.WithId(artistId)
.BuildArtist();
// Test ...
}
Un regard critique
Des effets de bord
[Test]
public void AnotherTestImplyingAnArtist()
{ ArtistBuilder builder = ArtistBuilder
.AnArtist()
.WhoSing("You Never Can Tell");
var anArtist = builder
.WithName("Chuck", "Berry")
.BuildArtist();
// ArtistBuilder mutate :
// builder -> FirstName = "Chuck"
// builder -> LastName = "Berry" }
Mutations vs Transformations
ArtistBuilder builder = ArtistBuilder
.AnArtist()
.WithName("Evelyne", "Gallet")
.WhoSing("La vieille");
// is just another way to write
ArtistBuilder builder = ArtistBuilder.AnArtist();
builder.WithName("Evelyne", "Gallet");
builder.WhoSing("La vieille");
// Because every method (WithName, WhoSing, ...) end with
return this;
Une approche fonctionnelle
Une approche fonctionnelle
public class ArtistBuilder
{
private Guid _artistId = Guid.NewGuid();
// ...
public ArtistBuilder WithId(Guid artistId)
{
var builder = Copy();
builder._artistId = artistId;
return builder;
}
private ArtistBuilder Copy()
=> new ArtistBuilder
{
_artistId = _artistId,
// other fields ...
};
// ...
}
Une approche fonctionnelle
public abstract class Record<TRecord>
where TRecord : Record<TRecord>
{ protected TRecord CopyWith(Action<TRecord> mutation)
{
var record = Copy();
mutation(record);
return record;
}
protected abstract TRecord Copy();
}
public class ArtistBuilder : Record<ArtistBuilder>
{
private Guid _artistId = Guid.NewGuid();
public ArtistBuilder WithId(Guid artistId)
=> CopyWith(builder => builder._artistId = artistId);
protected override ArtistBuilder Copy() // ...
}
Une approche fonctionnelle
type ArtistBuilder = { ArtistId:Guid; FirstName:string; LastName:string }
// unit → ArtistBuilder
let anArtist() =
// ...
// Guid → ArtistBuilder → ArtistBuilder
let withId artistId builder =
{ builder with ArtistId = artistId }
// Equivalent C#
// CopyWith(builder => builder._artistId = artistId)
// ArtistBuilder → Artist
let buildArtist builder =
// ...
// Guid → Artist
let artist artistId =
anArtist()
|> withId artistId
|> buildArtist
var artist = ArtistBuilder
.AnArtist()
.WithId(artistId)
.BuildArtist();
Merci !
Romain Berthon
#JobHacker
@RomainTrm
romainberthon.blog
Composabilité et associativité avec un langage objet
By Romain Berthon
Composabilité et associativité avec un langage objet
- 233