Support fluent programming style (enable method chaining)
vst-bit opened this issue · 4 comments
Hi all,
Many thanks for providing such a great code, first of all.
Would it be a lot of hassle for you to enhance the existing code generation a bit so we can all benefit from method chaining ❓
For instance, in case of Employee
type, there would be just two more types introduced (EmployeeSlice
and EmployeeSlicePtr
) as well as all their relevant methods like Map
, MapPtr
etc.:
type EmployeeSlice []Employee
// New method to generate
func (slice EmployeeSlice) Map(f func(Employee) Employee) EmployeeSlice {
return Map(f, slice) // reusing existing function
}
// further EmployeeSlice methods follow...
type EmployeeSlicePtr []*Employee
// New method to generate
func (slicePtr EmployeeSlicePtr) MapPtr(f func(*Employee) *Employee) EmployeeSlicePtr {
return MapPtr(f, slicePtr) // reusing existing function
}
// further EmployeeSlicePtr methods follow...
This would allow writing code in fluent programming style, something like:
es := EmployeeSlicePtr{&Employee{...}}
es = es.MapPtr(...).MapPtr(...).FilterPtr(...)
Furthermore, by simply rewriting the mapper function MapPtr
(in the monadic fashion using functors):
type EmployeeFunctorPtr func(*Employee) *Employee
type EmployeeMapFunctorPtr func(EmployeeFunctorPtr, EmployeeSlicePtr) EmployeeSlicePtr
// MapPtr function rewritten using functors
func MapPtr(f EmployeeFunctorPtr, slicePtr EmployeeSlicePtr) EmployeeSlicePtr {
if f == nil {
return EmployeeSlicePtr{}
}
newSlice := make(EmployeeSlicePtr, len(slicePtr))
for i, v := range slicePtr {
newSlice[i] = f(v)
}
return newSlice
}
...and by adding another (pipeline) method:
// Another method (to generate)
func (slicePtr EmployeeSlicePtr) MapPipelinePtr(mapper EmployeeMapFunctorPtr, functors ...EmployeeFunctorPtr) EmployeeSlicePtr {
tmpSlicePtr := slicePtr
for _, f := range functors {
tmpSlicePtr = mapper(f, tmpSlicePtr)
}
return tmpSlicePtr
}
...we would then also be able to simply chain any number of mapping transformations:
slice := &EmployeeSlicePtr{&Employee{1,"Alfa",...}, &Employee{2,"Beta",...}}
funct1 := func(e *Employee) *Employee {
...
return e
}
funct2 := func(e *Employee) *Employee {
...
return e
}
slice.MapPipelinePtr(MapPtr, funct1, funct2)
Thanks in advance for taking this into consideration.
Hi
I really appreciate you for taking the time to review and giving the ideas with examples.
I am thinking of adding your idea as a new feature. I am thinking something like this.
// https://play.golang.org/p/TvLbbx1CX83
package main
import "fmt"
func main() {
mySlice := Int32([]int32{1, 2, 3})
fmt.Println(
mySlice.
Filter(even).
Map(square).
Get())
}
type Int32 []int32
func(slice Int32) Map(f func(int32) int32) Int32 {
var newList Int32
for _, v := range slice {
r := f(int32(v))
newList = append(newList, r)
}
return newList
}
func(slice Int32) Filter(f func(int32) bool) Int32 {
var newList Int32
for _, v := range slice {
if f(int32(v)) {
newList = append(newList, v)
}
}
return newList
}
func (slice Int32) Get() []int32 {
return slice
}
func square(a int32) int32 {
return a * a;
}
func even(a int32) bool {
return a % 2 == 0
}
Let me know your feedback. It would be also great if you can put a running example that can help me to analyze and writing a template to generate code.
Hey, thanks! You're definitely on a good/right track! 👍
Here's a running example (for MapPtr method): https://goplay.tools/snippet/qZodFKHXUqO
package main
import "fmt"
// The source type to use for code generation
type Employee struct {
Id int
Name string
Salary float64
//CreationDate time.Time
}
// BEGIN OF CODE GENERATION
// Generate all required types (based on Employee)
type EmployeeSlicePtr []*Employee
type EmployeeFunctorPtr func(*Employee) *Employee
// The only explicit (and idempotent) constructor for the EmployeeSlicePtr type
func MakeEmployeeSlicePtr(employees ...*Employee) EmployeeSlicePtr {
newSlice := make(EmployeeSlicePtr, len(employees))
for i, e := range employees {
newSlice[i] = &Employee{Id: e.Id, Name: e.Name, Salary: e.Salary}
}
return newSlice
}
// A new helper using functors (analog existing MapPtr helper function)
func mapPtr(f EmployeeFunctorPtr, slicePtr EmployeeSlicePtr) EmployeeSlicePtr {
if f == nil {
return EmployeeSlicePtr{}
}
newSlice := make(EmployeeSlicePtr, len(slicePtr))
for i, v := range slicePtr {
newSlice[i] = f(v)
}
return newSlice
}
// The new mapping method (to generate)
func (slicePtr EmployeeSlicePtr) MapPtr(functors ...EmployeeFunctorPtr) EmployeeSlicePtr {
tmpSlicePtr := slicePtr
for _, f := range functors {
tmpSlicePtr = mapPtr(f, tmpSlicePtr)
}
return tmpSlicePtr
}
// END OF CODE GENERATION
// Various functors (to be used within transformation)
var (
tenPercentRaiseFunct EmployeeFunctorPtr = func(e *Employee) *Employee {
e.Salary *= 1.1
return e
}
incomeTaxFunct EmployeeFunctorPtr = func(e *Employee) *Employee {
e.Salary *= .95
return e
}
)
func main() {
emp1 := &Employee{1, "Alfa", 100.} // an object under transformation
emp2 := &Employee{2, "Beta", 200.} // yet another object under transformation
newSlice := MakeEmployeeSlicePtr(emp1, emp2).MapPtr(tenPercentRaiseFunct).MapPtr(incomeTaxFunct) // facilitate and run the transformation
// Equivalent to: newSlice := MakeEmployeeSlicePtr(emp1, emp2).MapPtr(tenPercentRaiseFunct, incomeTaxFunct)
fmt.Printf("Old salary (before raise) = %f => New salary (after raise) %f", emp1.Salary, newSlice[0].Salary)
// This should produce output: Old salary (before raise) = 100.000000 => New salary (after raise) 104.500000
}
Hi,
I completely agree with your idea. I will include this as a new feature in the next release sometime in March. Thanks a lot.
Hi!
Deeply grateful for adopting the idea 👍 And looking forward to the March release!
Here's the previous example rewritten to demonstrate some major benefits of using monads: https://goplay.tools/snippet/MrWJ-MXb5tF
package main
import "fmt"
// The source type to use for code generation
type Employee struct {
Id int
Name string
Salary float64
//CreationDate time.Time
}
//// BEGIN OF CODE GENERATION ////
//// TYPES
type EmployeeSlicePtr []*Employee // new (slice) type to generate methods for
type EmployeeFunctorPtr func(e *Employee, index int, length int) *Employee // EmployeeSlicePtr metadata-aware (inde/length) functor
type EmployeeMonadPtr func(...interface{}) EmployeeFunctorPtr // generic monad for EmployeeFunctorPtr
//// MONADS (field-specific)
// A type field-specific monadic helper (field: Salary)
var EmployeeSalaryMultiplicatorPtr EmployeeMonadPtr = func(flt64 ...interface{}) EmployeeFunctorPtr {
return func(e *Employee, index, length int) *Employee {
for _, f := range flt64 {
e.Salary *= f.(float64)
}
return e
}
}
// Yet another type field-specific monadic helper (field: Salary)
var EmployeeSalaryIncreaserPtr EmployeeMonadPtr = func(flt64 ...interface{}) EmployeeFunctorPtr {
return func(e *Employee, index, length int) *Employee {
for _, f := range flt64 {
e.Salary += f.(float64)
}
return e
}
}
//// MONADS (field-agnostic)
// A field-agnostic monadic helper demonstrating trivial usage of index/length
var EmployeeNilFirstPtr EmployeeMonadPtr = func(...interface{}) EmployeeFunctorPtr {
return func(e *Employee, index, length int) *Employee {
if index == 0 {
return nil
}
return e
}
}
// Yet another field-agnostic monadic helper demonstrating a trivial usage of index/length
var EmployeeNilLastPtr EmployeeMonadPtr = func(...interface{}) EmployeeFunctorPtr {
return func(e *Employee, index, length int) *Employee {
if index == length-1 {
return nil
}
return e
}
}
// Yet another field-agnostic monadic helper demonstrating a trivial usage of index/length
var EmployeeNilOddsPtr EmployeeMonadPtr = func(...interface{}) EmployeeFunctorPtr {
return func(e *Employee, index, length int) *Employee {
if index%2 == 0 {
return nil
}
return e
}
}
// Yet another field-agnostic monadic helper demonstrating a trivial usage of index/length
var EmployeeNilEvensPtr EmployeeMonadPtr = func(...interface{}) EmployeeFunctorPtr {
return func(e *Employee, index, length int) *Employee {
if index%2 == 0 {
return e
}
return nil
}
}
//// CONSTRUCTORS
// The only explicit (and idempotent) constructor of EmployeeSlicePtr
func NewEmployeeSlicePtr(employees ...*Employee) EmployeeSlicePtr {
newSlice := make(EmployeeSlicePtr, len(employees))
for i, e := range employees {
newSlice[i] = &Employee{Id: e.Id, Name: e.Name, Salary: e.Salary}
}
return newSlice
}
// The copy constructor of EmployeeSlicePtr
func CopyFromEmployeeSlicePtr(employees EmployeeSlicePtr) EmployeeSlicePtr {
return NewEmployeeSlicePtr(employees...)
}
//// METHODS (of EmployeeSlicePtr)
// A new helper using functors (analog existing MapPtr helper function)
func mapPtr(f EmployeeFunctorPtr, slicePtr EmployeeSlicePtr) EmployeeSlicePtr {
if f == nil {
return EmployeeSlicePtr{}
}
newSlice := make(EmployeeSlicePtr, len(slicePtr))
for i, v := range slicePtr {
if v != nil {
if emp := f(v, i, len(slicePtr)); emp != nil {
newSlice[i] = emp
}
}
}
return newSlice
}
// The new mapping method (to generate)
func (slicePtr EmployeeSlicePtr) MapPtr(functors ...EmployeeFunctorPtr) EmployeeSlicePtr {
tmpSlicePtr := slicePtr
for _, f := range functors {
tmpSlicePtr = mapPtr(f, tmpSlicePtr)
}
return tmpSlicePtr
}
// A monadic version of the mapping method
func (slicePtr EmployeeSlicePtr) MapMonadicPtr(m EmployeeMonadPtr, values ...interface{}) EmployeeSlicePtr {
tmpSlicePtr := slicePtr
f := m(values...)
tmpSlicePtr = mapPtr(f, tmpSlicePtr)
return tmpSlicePtr
}
//// END OF CODE GENERATION ////
//// USAGE EXAMPLE
// Various *Employee functors (to be used when mapping)
var (
raiseSalaryTenPercent = EmployeeSalaryMultiplicatorPtr(1.1)
deduceIncomeTax = EmployeeSalaryMultiplicatorPtr(.95)
raiseSalaryTenPercentAndDeduceIncomeTax = EmployeeSalaryMultiplicatorPtr(1.1, .95) // previous two functors combined/chained
deduceSyndicalMembership = EmployeeSalaryIncreaserPtr(-2.)
)
func main() {
emp1 := &Employee{1, "Alfa", 100.} // an object under transformation
emp2 := &Employee{2, "Beta", 200.} // yet another object under transformation
origin := NewEmployeeSlicePtr(emp1, emp2) // just for the sake of comparison (in the final report)
CopyFromEmployeeSlicePtr(origin). // a whole new slice of pointers
// MapPtr(raiseSalaryTenPercentAndDeduceIncomeTax). // equivalent to: MapPtr(raiseSalaryTenPercent, deduceIncomeTax)
MapMonadicPtr(EmployeeSalaryMultiplicatorPtr, 1.1, .95). // equivalent to: MapPtr(EmployeeSalaryMultiplicatorPtr(1.1, .95))
MapPtr(deduceSyndicalMembership). // equivalent to: MapMonadicPtr(EmployeeSalaryIncreaserPtr, -2.)
//MapPtr(EmployeeNilFirstPtr()). // just to demo it, no any reason to call it at all
MapPtr(func(e *Employee, index, length int) *Employee { // short report
fmt.Printf("%s: Old gross salary (before raise) = %f => New net salary (after raise) = %f \n",
e.Name, origin[index].Salary, e.Salary)
return e
})
// This should produce output:
// Alfa: Old gross salary (before raise) = 100.000000 => New net salary (after raise) = 102.500000
// Beta: Old gross salary (before raise) = 200.000000 => New net salary (after raise) = 207.000000
}