gogenericsfunctional-programming

How can I emulate `fmap` in Go?


I would like to emulate fmap in Go. A trivial example:

type S [A any] struct {
  contents A
}

type Functor [A any, B any] interface{
  fmap(f func(A)B) B
}

func (x S[A]) fmap (f func(A)B) S[B] {
  x.contents = f(x.contents)
  return x
}

This fails with: undefined: B with regards to the interface implementation. Is there a common workaround for this?


Solution

  • The combination of Go's generics and methods isn't as expressive as Haskell's typeclasses are; not yet, at least. In particular, as pointed out by kostix in his comment,

    Go permits a generic type to have methods, but, other than the receiver, the arguments to those methods cannot use parameterized types.

    (source)

    Since Go methods cannot introduce new type parameters, the only way to have access to B in your fmap method is to introduce it in the declaration of your Functor type, as you did. But that doesn't make sense because, according to category theory, a functor takes one type parameter, not two.

    This example may be enough to convince you that using generics and methods to emulate Haskell typeclasses in Go is a fool's errand.


    One thing you can do, though, is implement fmap, not as a method, but as a top-level function:

    package main
    
    import "fmt"
    
    type S[A any] struct {
        contents A
    }
    
    func Fmap[A, B any](sa S[A], f func(A) B) S[B] {
        return S[B]{contents: f(sa.contents)}
    }
    
    func main() {
        ss := S[string]{"foo"}
        f := func(s string) int { return len(s) }
        fmt.Println(Fmap(ss, f)) // {3}
    }
    

    (Playground)

    But just because you can doesn't mean that you should. Always ask yourself whether transposing an approach from some other language to Go feels right.