Parcours
/
Roc
Roc
/
Exercices
/
Personne à lentilles
Personne à lentilles

Personne à lentilles

Moyen

Instructions

Utilise des lentilles pour mettre à jour des enregistrements imbriqués (spécifique aux langages à données immuables).

Mettre à jour les champs d'enregistrements imbriqués et immuables est plutôt pénible. Le code pour ce genre de cas est aussi lourd que la structure est profonde. Si tu as, disons, une Person qui contient une Address, qui a une Street, qui a un Number, mettre à jour le Number nécessite de créer une nouvelle Street avec le nouveau Number, puis une nouvelle Address avec la nouvelle Street et, enfin, une nouvelle Person avec la nouvelle Address. Déjà perdu ?

Une solution à ce problème consiste à utiliser des lentilles.

Implémente plusieurs fonctions d'accès aux enregistrements à l'aide de lentilles. La suite de tests te permet aussi d'éviter complètement les lentilles, afin que tu puisses expérimenter différentes approches.

Les lentilles ne sont pas très utilisées en Roc, car la syntaxe de mise à jour des enregistrements couvre très bien la plupart des cas d'usage. Par exemple, prenons cet enregistrement imbriqué :

bob = { hair: { color: Brown, length: Long }, mouth: Smiling, height: 6.0 }

Maintenant, coupons les cheveux de Bob :

new_bob = { ..bob, hair: { ..bob.hair, length: Short } }

Pas besoin de lentilles ! Cela dit, même si les lentilles sont rares en Roc, elles sont courantes dans d'autres langages comme Haskell et Scala, et tu pourrais être curieux de savoir comment on peut les implémenter en Roc. C'est aussi un bon moyen de s'entraîner sur certaines fonctionnalités avancées de Roc, comme les types paramétrés et les enregistrements contenant des fonctions. Voici d'ailleurs une façon de représenter une lentille en Roc :

Lens(whole, part) := {
	get : whole -> part,
	set : whole, part -> whole,
}

Il s'agit d'un type paramétré : whole et part peuvent être n'importe quels types. whole est généralement un enregistrement, tandis que part peut être n'importe quel type de champ. Le champ get contient une fonction qui renvoie une part précise du whole donné, tandis que le champ set contient une fonction qui renvoie une copie du whole donné, mais avec une part précise remplacée par la part donnée.

Par exemple, voici une lentille pour obtenir ou définir les cheveux d'une personne :

hair_lens = {
	get: |person| person.hair,
	set: |person, hair| { ..person, hair },
}

On peut utiliser cette lentille pour cibler les cheveux de Bob, comme ceci :

bob_hair = bob |> hair_lens.get

Et on peut changer ses cheveux comme ceci :

new_hair = { ..bob.hair, length: Short }
new_bob = bob |> (hair_lens.set)(new_hair)

Remarque : les parenthèses autour de hair_lens.set sont obligatoires ; sans elles, Roc chercherait une fonction nommée set dans le type de hair_lens.

Les lentilles deviennent particulièrement utiles quand on commence à les composer, ce qui permet d'obtenir et de définir des parties profondément imbriquées. On peut y parvenir en implémentant une fonction comme celle-ci :

compose : Lens(outer, inner), Lens(inner, part) -> Lens(outer, part)

Une fois cette fonction implémentée, on peut s'en servir pour créer une lentille sur la longueur des cheveux, comme ceci :

length_lens = {
	get: |hair| hair.length,
	set: |hair, length| { ..hair, length },
}
hair_length_lens = compose(hair_lens, length_lens)

Maintenant, tu peux faire une nouvelle coupe à Bob :

set_hair_length = hair_length_lens.set
new_bob = bob |> set_hair_length(Short)

Tu voudras peut-être aussi implémenter une fonction qui permet d'appliquer n'importe quelle transformation à une partie imbriquée :

over : Lens(whole, part), whole, (part -> part) -> whole

Une fois implémentée, tu peux utiliser over comme ceci :

cut_hair = |current_length| match current_length {
	Long => Short
	Short => Bald
	Bald => Bald
}
new_bob = hair_length_lens |> over(bob, cut_hair)

Dans cet exercice, les tests vérifieront que ton code est capable de modifier différentes parties imbriquées de l'enregistrement imbriqué suivant :

test_person : Person
test_person = {
	name: { forenames: "Jane Joanna", surname: "Doe" },
	birth: {
		born_at: { street: "Longway", house_number: 1024, place: "Springfield", country: "United States" },
		born_on: { year: 1984, month: 4, day: 12 },
	},
	address: { street: "Shortlane", house_number: 2, place: "Fallmeadow", country: "Canada" },
}

Nous te recommandons de faire passer les tests d'abord en utilisant la syntaxe de mise à jour des enregistrements de Roc, puis d'essayer les lentilles à la place.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet
Roc Exercism

Prêt à commencer Personne à lentilles ?

Inscris-toi sur Exercism pour apprendre et maîtriser Roc avec 125 exercices, et un vrai mentorat humain, le tout gratuitement.