【问题标题】:How to use Monoid Scala?如何使用 Monoid Scala?
【发布时间】:2020-04-14 03:36:42
【问题描述】:
trait Monoid[A] {
  def op(a1: A, a2: A): A

  def zero: A
}

def mapMergeMonoid[K, V](V: Monoid[V]): Monoid[Map[K, V]] = new Monoid[Map[K, V]] {
    override def op(a1: Map[K, V], a2: Map[K, V]): Map[K, V] =
      (a1.keySet ++ a2.keySet).foldLeft(zero) {
        (acc, k) => acc.updated(k, V.op(a1.getOrElse(k, V.zero), a2.getOrElse(k, V.zero)))
      }

    override def zero: Map[K, V] = Map[K, V]()
  }

据我所知,我可以用这个 Monoid 连接 2 个地图。但我不明白,如何使用它。 我必须放入 (V: Monoid[V]) 参数以使用 op 方法,然后放 2 个地图。

【问题讨论】:

    标签: scala functional-programming scalaz scala-cats monoids


    【解决方案1】:

    假设我们要合并两个Map[Int, String]类型的地图

    val a1: Map[Int, String] = Map(1 -> "Picard")
    val a2: Map[Int, String] = Map(1 -> "Worf", 2 -> "Data")
    

    然后V 变成String 这意味着我们需要提供Monoid[String] 实例来指定Vs 的组合方式

    val stringMonoid: Monoid[String] = new Monoid[String] {
      override def op(a1: String, a2: String) = a1 + a2
      override def zero = ""
    }
    

    综合起来我们有

    mapMergeMonoid(stringMonoid).op(a1, a2)
    

    哪个输出

    res0: Map[Int,String] = Map(1 -> PicardWorf, 2 -> Data)
    

    从概念上讲,monoid 提供了一种组合值的方式,因此在定义如何组合 Map[K, V] 类型的映射时,我们还需要指定映射的值 V 如何组合自身。因此Monoid[V]Monoid[Map[K, V]]定义中的necessary构成元素:

    Map[A, B]Monoid 如果 BMonoid

    【讨论】:

    • 现在明白了,谢谢。使用 Scala Worksheet 代替普通文件真的更舒服吗?
    • @andrew17 我个人认为 Scala Worksheets 是一个很好的学习工具。在 Scala Worksheets 中尝试类型类时,请记住设置 Plain 评估模型。
    【解决方案2】:

    Monoid 是一个typeclass。因此,建议学习如何在 Scala 中对它们进行建模,它使用 implicits
    具体来说,Monoid[Map[K, V]] 的情况称为typeclass derivation,因为我们首先需要证明V 具有 Monoid,以便证明Map[K, V]一个,对于所有Ks

    这是定义此类 typeclass 的规范方法,以及它的实例以及它们的操作/语法。

    trait Monoid[A] {
      def op(a1: A, a2: A): A
    
      def zero: A
    }
    
    object Monoid {
      implicit final val IntMonoid: Monoid[Int] = 
        new Monoid[Int] {
          override final def op(i1: Int, i2: Int): Int =
            i1 + i2
    
          override final val zero: Int = 0
        }
    
      implicit def mapMonoid[K, V](implicit vm: Monoid[V]): Monoid[Map[K, V]] =
        new Monoid[Map[K, V]] {
          override final def op(m1: Map[K, V], m2: Map[K, V]): Map[K, V] =
            (m1.keySet | m2.keySet).foldLeft(this.zero) {
              case (acc, key) =>
                acc + (key -> vm.op(
                  m1.getOrElse(key, default = vm.zero),
                  m2.getOrElse(key, default = vm.zero)
                ))
            }
    
          override final val zero: Map[K, V] = Map.empty
        }
    }
    
    object syntax {
      object monoid {
        implicit class MonoidOps[A] (private val a1: A) {
          def |+| (a2: A)(implicit M: Monoid[A]): A =
            M.op(a1, a2)
        }
      }
    }
    

    然后你可以像这样使用它:

    import syntax.monoid._ // Provides the |+| operator.
    
    Map('a' -> 1, 'b' -> 2) |+| Map('b' -> 3, 'c' -> 5)
    // res: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 5, c -> 5)
    

    最后,值得一提的是,虽然我相信第一次手工完成这些事情对于真正了解它们的工作原理非常棒。鼓励使用提供这些抽象的稳定且可用于生产的库,例如 Cats

    scalafiddle

    【讨论】:

      猜你喜欢
      • 2012-04-02
      • 1970-01-01
      • 1970-01-01
      • 2021-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多