如何更改幺半群以选择endofunctor?

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

我想在

Option
的背景下组合两个endofunctor。我想要的组合是通过
Category.compose
将两个内函子组合成一个。我发现
MonoidK[Endo].algebra[*]
Semigroup[Endo[*]]
实例满足我的要求。我正在使用
Semigroup[Endo[*]]
使
Option
成为幺半群
Endo

这是我实现它的尝试,但它结合了两个内函数,而不是我想提供的

Semigroup[Endo[*]]
,而是通过
cats.kernel
的一些
A => B
半群实例。

import cats.*
import cats.syntax.all.*
import cats.implicits.*

val f: Endo[Int] = _ + 1
val g: Endo[Int] = _ + 2

val of = f.pure[Option]
val og = g.pure[Option]

given Semigroup[Endo[Int]] = MonoidK[Endo].algebra[Int]
val oh = Monoid[Option[Endo[Int]]].combine(of, og)

val res1 = // Some(9)
  oh.mapApply(3)

val res2 = // Some(6)
  Semigroup[Endo[Int]].combine(f, g).apply(3).pure[Option]

res2
是我想要的结果。我该如何解决它?

scala functional-programming scala-cats
1个回答
0
投票

让我们添加一些调试实用程序:

// subtype of Endo[Int] which would print debug info in evaluation
case class EndoImpl(name: String, added: Int) extends (Int => Int) {

  def apply(a: Int): Int = {
    println(s"$name($a)==$a+$added")
    a + added
  }

  override def compose[A](g: A => Int): A => Int = g.andThen(this)

  override def andThen[A](g: Int => A): Int => A = (this, g) match {
    case (f1: EndoImpl, g1: EndoImpl) =>
      EndoImpl(s"(${f1.name} andThen ${g1.name})", f1.added + g1.added)
        .asInstanceOf[Int => A]
    case _ => super.andThen(g)
  }
}

那么我们稍微修改一下示例吧

val f: Endo[Int] = EndoImpl("f", 1)

val g: Endo[Int] = EndoImpl("g", 2)

val of = f.pure[Option]
val og = g.pure[Option]

given Semigroup[Endo[Int]] = MonoidK[Endo].algebra[Int]
val oh: Option[Endo[Int]] = Monoid[Option[Endo[Int]]].combine(of, og)

println("oh.mapApply(3)")
println(
  oh.mapApply(3)
)
println()

println("Semigroup[Endo[Int]].combine(f, g).apply(3).pure[Option]")
println(
  Semigroup[Endo[Int]].combine(f, g).apply(3).pure[Option]
)

打印:

oh.mapApply(3)
f(3)==3+1
g(3)==3+2
Some(9)

Semigroup[Endo[Int]].combine(f, g).apply(3).pure[Option]
(g andThen f)(3)==3+3
Some(6)

有趣:

  • 第一个实现不使用我们的
    andThen
    也不使用
    combine
    ,因此它不是使用其中任何一个组合 Endo 值的幺半群
  • 同时,第二个实现具有
    combine
    功能,并且
    apply

让我们尝试挖掘更多。 IntelliJ 和 VC Code+metals 都有用于预览隐式传递参数或用于转换的实用程序。

他们让我们看到第二个(预期)实现大致等于:

val om: Option[Endo[Int]] = cats.kernel.instances.option.catsKernelStdMonoidForOption[Endo[Int]](
  summon[Semigroup[Endo[Int]]]
).combine(of, og)

按预期工作:

  • 我们将 Some 中的功能组合起来:
    (_ + 1) combine (_ + 2)
  • 如果不是,那么我们调用最终函数
    None

同时意想不到的实现是:

catsKernelStdCommutativeMonoidForOption[Endo[Int]](
  catsKernelCommutativeGroupForFunction1[Int, Int]
).combine(of, og).mapApply(3)

即:

  • 单独调用每个函数
    (_ + 1)(3)
    ,`(_ + 2)(3)
  • 然后将每次调用的结果与
    Semigroup[Int]
    相结合:
    4 + 5

这是一个隐式优先级的问题,我将通过手动构建预期实例并将其公开为

given
来解决它,而不需要进行猜测工作的优先级。

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