Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translating types #11

Open
sirfilip opened this issue Sep 2, 2022 · 10 comments
Open

Translating types #11

sirfilip opened this issue Sep 2, 2022 · 10 comments

Comments

@sirfilip
Copy link

sirfilip commented Sep 2, 2022

At this moment we cant use monads to translate between different Result monad types.

Ex
Ok(42).FlatMap(func(int) Result[string] { return Ok("wont work") })
because of the single type constraint introduced in the signature.

It wold be very useful if we can perform translations.

A way to do this is to detach FlatMap from result and make the signature like this

func [T, U any] FlatMap(func(T) Result[U]) Result[U]

Or maybe even

func [T, U any] FlatMap(func(T)(U, error)) Result[U]

I understand why this is not done here, it is because of the go generics restriction not to introduce new types in a struct methods. At the time being all types that are used in the struct methods must be declared in the struct definition.

Also func(T) Result[U] is not a correct functor interface, for go maybe func(t) (U, error) would be more appropriate but tbh returning result feels right. The cons is that it will be hard to define universal interface that will work across all of the monads.

@samber
Copy link
Owner

samber commented Sep 4, 2022

I 100% agree.

We are looking for a way to implement common interfaces. Any idea appreciated ;)

@exchgr
Copy link

exchgr commented Sep 16, 2022

As it is now:

func (o Option[T]) FlatMap(mapper func(value T) Option[T]) Option[T] {
	if o.isPresent {
		return mapper(o.value)
	}

	return None[T]()
}

Could this work?

func (o Option[T]) FlatMap[R](mapper func(value T) Option[R]) Option[R] {
	if o.isPresent {
		return mapper(o.value)
	}

	return None[R]()
}

@sirfilip
Copy link
Author

sirfilip commented Sep 16, 2022 via email

@exchgr
Copy link

exchgr commented Sep 19, 2022

Ah, right you are

@joel-u410
Copy link
Contributor

joel-u410 commented Sep 20, 2022

A while back, I had written a similar type (though I named my package rs because it was inspired by Rust!) and ended up just having a top-level Map function.

type Option[T any] struct {
	value *T
}
func Map[T any, U any](o Option[T], fn func(T) U) Option[U] {
	switch {
	case o.IsSome():
		return Some(fn(o.Unwrap()))
	default:
		return None[U]()
	}
}

Though, it might be tricky to make that work for both Option and Result -- which you'd probably want. Maybe you could have a Mappable interface, which provides the necessary support for implementing Map in a generic way, and then Map could be something like Map[M Mappable, T any, U any]. I haven't tried it though, it might still run up against the lack of generic methods. 😞

@exchgr
Copy link

exchgr commented Sep 25, 2022

That signature is similar to lo’s Map, which is for iterating over slices. (I sometimes think of Options as slices with at most one element, so mapping them makes sense, and so does the similar function signature.) I think while go doesn’t allow generic methods (functions tied to structs), it allows generic functions (top-level), so something like this would work.

@tbflw
Copy link

tbflw commented Nov 9, 2022

We're starting to look into using this lib in our Go stack. The lack of mapping/transform functions for the different algenraic data types is a bit f nuisance, so I really support the idea of doing something about that. IMO, the only way of doing that (due to the already mentioned (silly) constraints in the Go generics implementation for types), is to add top level functions for each type. Internally, we for example have defined a function something like this:

func TransformEither[L, R, T any](either mo.Either[L, R], tLeft func(L) T, tRight func(R) T) T {
	if either.IsLeft() {
		return tLeft(either.MustLeft())
	}
	return tRight(either.MustRight())
}

would it make sense to add a package either containing these top level functions? Actually, IMO, it would also make sense to have type constructors in that package, so you could write v := either.Right[L,R](someValue).

@tbflw
Copy link

tbflw commented Nov 9, 2022

@samber I'd be happy to contribute some PRs if we can agree on a direction for the structure.

@GiGurra
Copy link

GiGurra commented Mar 12, 2023

I guess the limiting factor is that go does not allow type parameters on methods? If that was possible, transforming the value would be simple. There is an ongoing (been going on for years) discussion about adding support for this, but no conclusion :S

golang/go#49085

Basically they haven't found any practical way of doing it because of how Golang implements generics (similar to how C++ templates). If they disallow type parameters for interface methods, it can be done though. I am hoping they will implement that in some version of the language :S

https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods

Stack overflow with discussion about the same thing being impossible in C++
https://stackoverflow.com/questions/3251549/creating-an-interface-for-an-abstract-class-template-in-c.

Ofc, in other languages that are relying on erasure instead of code generation for generics (which is inferior in many other ways), the problem goes away entirely :S

@tperdue321
Copy link

tperdue321 commented May 31, 2023

@tbflw @samber I've also been thinking the same thing and would be happy to contribute PRs that add functors for the various types. an adhoc example of functor

func Map[T any, U any](option mo.Option[T], mapper func(value T) (U, bool)) mo.Option[U] {
	val, present := option.Get()
	if present {
		return mo.TupleToOption(mapper(val))
	}
	return mo.None[U]()
}

inspiration coming from https://typelevel.org/cats/typeclasses/functor.html

(edit/late addition to my comment): While it would be ideal to use type parameters on methods, since Go doesn't support that, I think that creating top level functors (maybe scoped by packages that are named by types?) is an acceptable best-efforts alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants
@sirfilip @exchgr @GiGurra @samber @tperdue321 @tbflw @joel-u410 and others