+
Skip to content

csgura/fp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

github.com/csgura/fp

This package is inspired by Haskell, Scala, Shapeless, and Scala Cats.
Following features are provided by this package.

  • Algebraic data types such as Tuple and Option
  • A code generating tool gombok which is a lombok-like that can generate getter and builder methods.

1. How to install gombok

Install gombok using:

go install github.com/csgura/fp/cmd/gombok

2. @fp.Value

@fp.Value annotation is used to generate getters and builder.

Example:

package docexample
 
//go:generate gombok
 
// @fp.Value
type Person struct {
    name string
    age  int
}

go:generate only needs to be specified once in a package.

@fp.Value must be specified to each struct to generate getter method.

Getter method will not generated if fields name starts with an uppercase letter or starts with _ .

Run go generate to generate the code. gombok creates {packname}_value_generated.go file and adds the generated code in it.

The following types and methods are created in the generated file.

2.1. Getter method

func (r Person) Name() string {
    return r.name
}
 
func (r Person) Age() int {
    return r.age
}
 

2.2. String method

func (r Person) String() string {
    return fmt.Sprintf("Person(name=%v, age=%v)", r.name, r.age)
}

2.3. With method

func (r Person) WithName(v string) Person {
    r.name = v
    return r
}
 
 
func (r Person) WithAge(v int) Person {
    r.age = v
    return r
}

2.4 AsTuple method

func (r Person) AsTuple() fp.Tuple2[string, int] {
    return as.Tuple2(r.name, r.age)
}

2.5. AsMap method

func (r Person) AsMap() map[string]any {
    return map[string]any{
        "name": r.name,
        "age":  r.age,
    }
}

2.6. Builder type and conversion method

type PersonBuilder Person
 
 
 
func (r PersonBuilder) Build() Person {
    return Person(r)
}
 
 
func (r PersonBuilder) Name(v string) PersonBuilder {
    r.name = v
    return r
}
 
 
func (r PersonBuilder) Age(v int) PersonBuilder {
    r.age = v
    return r
}
 
func (r PersonBuilder) FromTuple(t fp.Tuple2[string, int]) PersonBuilder {
    r.name = t.I1
    r.age = t.I2
    return r
}
 
 
func (r PersonBuilder) FromMap(m map[string]any) PersonBuilder {
 
    if v, ok := m["name"].(string); ok {
        r.name = v
    }
 
    if v, ok := m["age"].(int); ok {
        r.age = v
    }
 
    return r
}
func (r Person) Builder() PersonBuilder {
    return PersonBuilder(r)
}

2.7. Mutable type and conversion method

type PersonMutable struct {
    Name string
    Age  int
}
 
 
func (r PersonMutable) AsImmutable() Person {
    return Person{
        name: r.Name,
        age:  r.Age,
    }
}
func (r Person) AsMutable() PersonMutable {
    return PersonMutable{
        Name: r.name,
        Age:  r.age,
    }
}

2.8. equals and hashCode method

Unlike lombok, gombok doesn't generate equals and hashCode methods because they don't need.
If it required for some other reason,
You can use an instance of the fp.Eq or fp.Hashable typeclass to define equals and hashCode method, which can be genereted by gombok

// @fp.Derive
var _ hash.Derives[fp.Hashable[Person]]

// var HashablePerson fp.Hashable[Person] 
// will be generated by above @fp.Derive annotation

// you can define Eq method by calling HasablePerson.Eqv
func (r Person) Eq(other Person) bool {
    return HashablePerson.Eqv(r, other)
}
 
func (r Person) Hashcode() uint32 {
    return HashablePerson.Hash(r)
}
 

2.9. fp.Option support

// @fp.Value
type User struct {
    name   string
    email  fp.Option[string]
    active bool
}

If field type is Option, the following Option-related methods are added.

func (r User) WithSomeEmail(v string) User {
    r.email = option.Some(v)
    return r
}
 
func (r User) WithNoneEmail() User {
    r.email = option.None[string]()
    return r
}
 
 
func (r UserBuilder) SomeEmail(v string) UserBuilder {
    r.email = option.Some(v)
    return r
}
 
func (r UserBuilder) NoneEmail() UserBuilder {
    r.email = option.None[string]()
    return r
}

3. @fp.Json

// @fp.Value
// @fp.Json
type Address struct {
    country string
    city    string
    street  string
}

3.1. Json tag of mutable type

type AddressMutable struct {
    Country string `json:"country,omitempty"`
    City    string `json:"city,omitempty"`
    Street  string `json:"street,omitempty"`
}

Json tags will be added to the fields of mutable type.
If the json tag is already defined, it is just copied.

3.2. MasharlJSON, UnmarshalJSON

func (r Address) MarshalJSON() ([]byte, error) {
    m := r.AsMutable()
    return json.Marshal(m)
}
 
func (r *Address) UnmarshalJSON(b []byte) error {
    if r == nil {
        return fp.Error(http.StatusBadRequest, "target ptr is nil")
    }
    m := r.AsMutable()
    err := json.Unmarshal(b, &m)
    if err == nil {
        *r = m.AsImmutable()
    }
    return err
}

As above, MarshalJSON and UnmarshalJSON methods are generated.

4. @fp.GenLabelled

@fp.GenLabelled annotation is required when deriving an instance of a typeclasse that use field names, such as json encoders and decoders.

// @fp.Value
// @fp.GenLabelled
type Car struct {
    company string
    model   string
    year    int
}

4.1. AsLabelled method

func (r Car) AsLabelled() fp.Labelled3[NamedCompany[string], NamedModel[string], NamedYear[int]] {
    return as.Labelled3(NamedCompany[string]{r.company}, NamedModel[string]{r.model}, NamedYear[int]{r.year})
}

4.2. FromLabelled method

func (r CarBuilder) FromLabelled(t fp.Labelled3[NamedCompany[string], NamedModel[string], NamedYear[int]]) CarBuilder {
    r.company = t.I1.Value()
    r.model = t.I2.Value()
    r.year = t.I3.Value()
    return r
}

4.3. Labelled type

type NamedCompany[T any] fp.Tuple1[T]
 
func (r NamedCompany[T]) Name() string {
    return "company"
}
func (r NamedCompany[T]) Value() T {
    return r.I1
}
func (r NamedCompany[T]) WithValue(v T) NamedCompany[T] {
    r.I1 = v
    return r
}

5. Types that have type parameters.

// @fp.Value
type Entry[A comparable, B any] struct {
    key   A
    value B
}
type EntryBuilder[A comparable, B any] Entry[A, B]
 
type EntryMutable[A comparable, B any] struct {
    Key   A
    Value B
}
 
func (r Entry[A, B]) Key() A {
    return r.key
}
 
func (r Entry[A, B]) WithKey(v A) Entry[A, B] {
    r.key = v
    return r
}

6. @fp.Derive

gombok can generates an instance of a typeclass automatically.
@fp.Derive annotation is used.

Example:

// @fp.Derive
var _ eq.Derives[fp.Eq[Person]]

fp.Eq[Person] is target type to be generated.
Person type should have @fp.Value annotation so that AsTuple method and PersonBuilder type are generated by gombok

eq.Derives means eq package has typeclass instances of primitive types ( e.g. Tuple, String , HCons , HNil ) , and these instances will be used to generate fp.Eq[Person]

eq.Derives is a phantom type and has no functionality.
It was just used to tell gombok about the eq package and target type.

package eq
type Derives[T any] interface {
	
}

The generated codes is like this:

var EqPerson fp.Eq[Person] = eq.ContraMap(
    eq.Tuple2(eq.String, eq.Given[int]()),
    Person.AsTuple,
)

In the above code, gombok summons three typeclass instances from eq package to make Eq[Tuple2[string,int]]

  • eq.Tuple2()
  • eq.String
  • eq.Given[int]()

And eq.ContrMap is used to convert Eq[Tuple2[string,int]] to Eq[Person]
eq.ContraMap function requires a function which converts Person to Tuple2[string,int], so Person.AsTuple method is used which is generated by @fp.Value annotation.

6.1. typeclass instance naming rule

Instances of typeclasses are summoned by name.
Here are the rules for names.

Target
Type
Example Local Package of Type Derive Package
Go named type time.Duration EqTimeDuration, EqDuration time.EqDuration eq.TimeDuration, eq.Duration
Tuple ( 1 ~ 21 ) Tuple2 EqTuple2[A,B any)(Eq[A], Eq[B]) Tuple2[A,B any)(Eq[A], Eq[B])
hlist.Cons hlist.Cons EqHCons[H any,T hlist.HList](Eq[H], Eq[T]) eq.HCons[H any,T hlist.HList](Eq[H], Eq[T])
hlist.Nil hlist.Nil EqHNil eq.HNil
Slice []string EqSlice( Eq[string] ) eq.Slice(Eq[string))
Map map[string]any EqGoMap(Eq[string], Eq[any]) eq.GoMap(Eq[string], Eq[any])
[]byte []byte EqBytes, EqSlice(Eq[byte]) eq.Bytes, eq.Slice(Eq[byte])
Pointer *int EqPtr(lazy.Eval[Eq[int]]) eq.Ptr(lazy.Eval[Eq[int]])
Basic int EqInt eq.Int
number float64 EqNumber[~float64 | ~int]() eq.Number[~float64 | ~int]()
comparable or any comparable EqGiven[comparable]() eq.Given[comparable]()
Labelled ( 1 ~ 21 ) Labelled2 EqLabelled2[A,B fp.Named)(Eq[A], Eq[B]) eq.Labelled2[A,B fp.Named)(Eq[A], Eq[B])
hlist.Cons Labelled hlist.Cons EqHConsLabelled[H fp.Named, T hlist.HList)(Eq[H], Eq[T]) eq.HConsLabelled[H fp.Named, T hlist.HList)(Eq[H], Eq[T])
fp.Named fp.Named EqNamed[T fp.NamedField[A], A any)(Eq[A]) eq.Named[T fp.NamedField[A], A any)(Eq[A])

6.2. typeclass variant

After creating an instance of a Tuple or hlist.Cons, gombok converts it to an instance of that type.
In the case of the eq typeclass, ContraMap was used for conversion, but the function used for conversion changes according to the typeclass's variant.

Because gombok doesn't know how to convert, one of ContraMap, IMap, Map and Generic functions must be provided.

typeclass variant Function must be provided Example typeclass
Covariant Map or Generic Decoder, Read
Contravariant ContraMap or Generic Eq, Ord, Encoder, Show
Invariant IMap or Generic Monoid

ContraMap should have the following form.

func ContraMap[A, B any](instance fp.Eq[A], fba func(B) A) fp.Eq[B] {
	return New(func(a, b B) bool {
		return instance.Eqv(fn(a), fba(b))
	})
}

Map should have the following form.

func Map[A, B any](aread Read[A], fab func(A) B) Read[B] {
	return New(func(s string) fp.Try[Result[B]] {
		return try.Map(aread.Reads(s), func(r Result[A]) Result[B] {
			return MapResult(r, fab)
		})
	})
}

IMap should have the following form.

func IMap[A, B any](instance fp.Monoid[A], fab func(A) B, fba func(B) A) fp.Monoid[B] {
	return New(func() B {
		return fab(instance.Empty())
	}, func(a, b B) B {
		return fab(instance.Combine(fba(a), fba(b)))
	})
}

Generic should have the following form.

func Generic[A, Repr any](gen fp.Generic[A, Repr], reprShow fp.Show[Repr]) fp.Show[A] {
	return New(func(a A) string {
		return fmt.Sprintf("%s(%s)", gen.Type, reprShow.Show(gen.To(a)))
	})
}

fp.Generic is following type.

package fp

type Generic[T, Repr any] struct {
	Type string
	To   func(T) Repr
	From func(Repr) T
}

6.3. Deriving instances for recursive types

The Ptr instance should use lazy.Eval to avoid infinite loops.

func Ptr[T any](eq lazy.Eval[fp.Eq[T]]) fp.Eq[*T] {
	return New(func(a, b *T) bool {
		if a == nil && b == nil {
			return true
		}

		if a != nil && b != nil {
			return eq.Get().Eqv(*a, *b)
		}

		return false
	})
}

Example Recursive type :

// @fp.Value
type Node struct {
	value string
	left  *Node
	right *Node
}

// @fp.Derive
var _ eq.Derives[fp.Eq[Node]]

Generated Code :

func EqNode() fp.Eq[Node] {
	return eq.ContraMap(
		eq.Tuple3(eq.String, eq.Ptr(lazy.Call(func() fp.Eq[Node] {
			return EqNode()
		})), eq.Ptr(lazy.Call(func() fp.Eq[Node] {
			return EqNode()
		}))),
		Node.AsTuple,
	)
}

EqNode summoned as func because it calls itself recursively.

7. @fp.ImportGiven

// @fp.ImportGiven
var _ ord.Derives[fp.Ord[any]]

You can import typeclass instances defined in other packages.

Example:

// @fp.ImportGiven
var _ ord.Derives[fp.Ord[any]]

// EqSeq is an overrided instance of eq.Seq
// as a result of importing, you can use fp.Ord while deriving fp.Eq[fp.Seq]
func EqSeq[T any](eqT fp.Eq[T], ordT fp.Ord[T]) fp.Eq[fp.Seq[T]] {
	return eq.New(func(a, b fp.Seq[T]) bool {
		asorted := seq.Sort(a, ordT)
		bsorted := seq.Sort(b, ordT)
		return eq.Seq(eqT).Eqv(asorted, bsorted)
	})
}

// @fp.Value
type TestOrderedEq struct {
	list  fp.Seq[int]
	tlist fp.Seq[fp.Tuple2[int, int]]
}

// @fp.Derive
var _ eq.Derives[fp.Eq[TestOrderedEq]]

Generated Code:

// as a result, 
// EqSeq is summoned instead of eq.Seq,
// and Given func is summoned, which defined in ord package.
var EqTestOrderedEq = eq.ContraMap(
	eq.Tuple2(EqSeq(eq.Given[int](), ord.Given[int]()), EqSeq(eq.Tuple2(eq.Given[int](), eq.Given[int]()), ord.Tuple2(ord.Given[int](), ord.Given[int]()))),
	TestOrderedEq.AsTuple,
)

8. typeclass example

About

functional programming in Go

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载