我想在 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 的泛型和方法的组合并不像 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 感觉是否正确。
我想补充一点,您遇到的一些问题是您从错误的定义开始的。提议中应该有一些直接的危险信号
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 在链接答案中指出的那样,方法可能不接受额外的类型参数。
虽然 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 还很陌生,所以我还在自己学习。