如何在 Go 中模拟 `fmap`?

问题描述 投票:0回答:3

我想在 Go 中模拟

fmap
。一个简单的例子:

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
}

这失败了:

undefined: B
关于
interface
实施。有没有通用的解决方法?

go generics functional-programming
3个回答
2
投票

Go 的泛型和方法的组合并不像 Haskell 的类型类那样富有表现力;至少现在还没有。特别是,正如 kostix 在他的评论

中指出的那样

Go 允许泛型类型拥有方法,但是,除了接收者之外,这些方法的参数不能使用参数化类型。

来源

由于 Go 方法无法引入新的类型参数,因此在

B
方法中访问
fmap
的唯一方法是在
Functor
类型的声明中引入它,就像您所做的那样。但这没有意义,因为根据范畴论,函子采用一个类型参数,而不是两个。

这个例子可能足以让你相信,在 Go 中使用泛型和方法来模拟 Haskell 类型类是一件愚蠢的事。


但是,您可以做的一件事是实现

fmap
,不是作为方法,而是作为顶级函数:

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}
}

(游乐场)

但仅仅因为你可以,并不意味着你应该这样做。总是问自己,将某种方法从其他语言移植到 Go 感觉是否正确。


0
投票

我想补充一点,您遇到的一些问题是您从错误的定义开始的。提议中应该有一些直接的危险信号

Functor
-

type Functor [A any, B any] interface{
                  // ^ Functor should wrap a single type ⚠️
  fmap(f func(A)B) B
                // ^ should return Functor-wrapped B ⚠️
}

解决您上面遇到的问题,这就是我们喜欢写的 -

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

但是 Go 警告我们就您面临的问题向我们提供直接反馈 -

interface method must have no type parameters
undefined: B

正如 @jub0bs 在链接答案中指出的那样,方法可能不接受额外的类型参数


0
投票

虽然 Go 的泛型 + 接口方法确实不如 Haskell 的类型类或 Scala 的泛型 + 隐式 + 特征扩展方法那么富有表现力,但它仍然具有足够的表现力来实现这一点。

关键因素是在通用数据类型中包含“幻像”类型,以避免出现

undefined: B
错误。因此,您的
struct
的稍微修改版本是:

type S[A any, B any] struct {
    contents A
} // B is our phantom type here

此外,由于 Go 不支持更高种类的类型(HKT),因此您创建的

Functor interface
必须比真正的
Functor
更通用。然后,您可以通过嵌入接口的实现来强制执行正确的类型约束。这里稍微修改一下的接口声明是:

type Functor[A any, B any, C any] interface{
  fmap(f func(A)B) C
} // C is more general than it needs to be

现在实现接口...

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

最后,测试一下...

func main(){
  a := S[int,string]{contents: 7}
  h := func(t int)string {return "the answer is 42, duh!"}
  fmt.Println(a.fmap(h)) // {the answer is 42, duh!}
}

为了让它与简单的 Go 数组一起工作,我发现我需要将数组打包为命名类型。为此,您需要执行以下操作...

type GhostArray[A any, B any] []A

数组的实现可能看起来像……(提前道歉,因为这是一个命令式实现……如果您愿意,可以使用尾递归来实现)

func (xs GhostArray[A,B]) fmap(f func(A) B) GhostArray[B,A] {
  y := make([]B,len(xs))
  for i := 0; i < len(xs); i++ {
    y[i] = f(xs[i])
  }
  return y
}

请注意,在这种情况下,您可以将返回类型替换为

[]B
。 Go 仅对接收者是命名类型很挑剔。

上面的结构主要是在简单性方面犯了错误。我很好奇如何扩展它以更好地解释嵌入式类型。我对 Go 还很陌生,所以我还在自己学习。

© www.soinside.com 2019 - 2024. All rights reserved.