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) = (hg) ∘ 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) = (hg) ∘ 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