Polymorphic Optics #
The optics we have used so far have been monomorphic. This means that the output source and focus types are the same as the input types. This can be seen in this visualisation.
The types flowing to the right are identical to this flowing to the left. This is the normal case when working with optics created by makelens.
It is however possible to convert datatypes within an optic. For these use cases you will need to create user defined optics to perform the conversion.
We can imagine a use case where we are upgrading the rating datatype from int to float64 value. If we could construct an optic with this structure it would be possible.
This is in fact possible with polymorphic optics.
package main
import (
"fmt"
. "github.com/spearson78/go-optic"
"github.com/spearson78/go-optic/expr"
)
// V1 data types
type BlogPost struct {
author string
title string
content string
comments []Comment
ratings []Rating
}
type Comment struct {
author string
title string
content string
}
type Rating struct {
author string
stars int
}
// V2 data types
type BlogPostV2 struct {
author string
title string
content string
comments []Comment
ratings []RatingV2
}
type RatingV2 struct {
author string
stars float64
}
func ExamplePolymorphism() {
upgradeBlogRatings := LensP[BlogPost, BlogPostV2, []Rating, []RatingV2](
func(source BlogPost) []Rating {
return source.ratings
},
func(source BlogPost, focus []RatingV2) BlogPostV2 {
return BlogPostV2{
//Retain the fields that don't need an upgrade
author: source.author,
title: source.title,
content: source.content,
comments: source.comments,
//Use the upgraded focus for ratings
ratings: focus,
}
},
ExprCustom("upgradeBlogRatings"),
)
upgradeTraverseSlice := TraverseSliceP[Rating, RatingV2]()
upgradeRatingStars := LensP[Rating, RatingV2, int, float64](
func(source Rating) int {
return source.stars
},
func(source Rating, focus float64) RatingV2 {
return RatingV2{
author: source.author,
stars: focus,
}
},
ExprCustom("upgradeRatingStars"),
)
upgradeOptic := Compose3(
upgradeBlogRatings,
upgradeTraverseSlice,
upgradeRatingStars,
)
var result BlogPostV2 = MustModify(
upgradeOptic,
IsoCast[int, float64](),
BlogPost{
author: "Max Mustermann",
title: "Polymorphic Optics",
content: "Lorem ipsum",
comments: []Comment{},
ratings: []Rating{
{
author: "Max Mustermann",
rating: 5,
},
},
})
fmt.Println(result)
//Output:
//{Max Mustermann Polymorphic Optics Lorem ipsum [] [{Max Mustermann 0.5}]}
}
In order to change types we have to use the polymorphic version of a lens created with the LensP constructor.
During the modify we are given the old version of the source and the new version of the focus. We can then perform the upgrade by copying the old values into the new version making sure to use the updated focus in the new copy.
We did not have to implement the upgrade for the slice as TraversSlice has a polymorphic version TraverseSliceP where possible all built in functions have a polymorphic version with the P postfix.