从下面的例子中,我认为String
在连接操作下定义一个monoid是正确的,因为它是一个关联二进制操作,而String
碰巧有一个标识元素是一个空字符串""
。
scala> ("" + "Jane") + "Doe" == "" + ("Jane" + "Doe")
res0: Boolean = true
从我最近阅读过的关于这个主题的各种文本来看,似乎正确使用术语 monoid 是幺半群实际上是两种类型的组合(在这种情况下{ {1}})和一个monoid类型的实例,它定义了操作和标识元素。
例如,这是一个理论上的String
类型及其具体实例,似乎在各种书籍/文章中经常被定义: -
Monoid
我知道我们不需要在核心(Scala,或者更确切地说是Java)库中定义trait Monoid[A] {
def op(a1: A, a2: A): A
def zero: A
}
val stringMonoid = new Monoid[String] {
def op(a1: String, a2: String) = a1 + a2
val zero = ""
}
或trait Monoid[A]
来支持我的REPL输出,并且该示例只是一个理解的工具幺半群的抽象概念。
我的问题(我很可能会考虑太多)是纯粹的定义。我知道底层的stringMonoid
(或更确切地说java.lang.String
)已经定义了关联操作,但我认为没有明确的identity元素定义(在这种情况下只是一个空字符串{{ 1}})定义在任何地方。
问题: -
StringBuilder
隐式地在连接操作下是一个monoid,只是因为我们碰巧知道使用空字符串""
提供了身份?或者,对于类型的标识元素的明确定义是不必要的,因为它被归类为monoid(在特定的关联二元操作下)。
答案 0 :(得分:4)
你可能会过度思考这一点。 monoid 是一种抽象概念,存在于编程和类型系统之外(一种类别理论)。考虑定义:
monoid是一个在关联二元操作下关闭并具有标识元素的集合。
您已将String
类型标识为已关闭
使用关联二进制操作(串联)和标识元素(空字符串)进行设置。语言可能没有明确告诉你身份元素是什么,但这并不意味着它不存在(我们知道它确实存在)。具有连接二进制操作的String
类型肯定是 monoid ,因为您可以证明它符合所有上述属性。
创建Monoid
类型类仅仅是为了方便我们处理使用monoids运行的通用数据结构。无论编程语言(或其他一些库)是否明确说明什么设置与什么二元操作和什么身份构成幺半群,幺半群仍然存在。
重要的是要注意String
类型本身并不构成monoid,因为它必须与所述二元运算等存在。可能有另一个monoid使用相同的set存在,但是一个不同的二元操作。
答案 1 :(得分:2)
我认为你已经理解了这个概念是正确的,并且确实已经过多地思考了#34;关于它(;
monoid是一个三元组正如他们在数学中所说:一个集合(想象一个带有它的值的类型),一个关联的二元运算符和一个中性元素。你定义这样的三元组 - 你定义一个幺半群。 <答案
隐式地在
String
串联操作下是一个monoid,只是因为我们碰巧知道使用空字符串""
提供了身份吗?
只是是。你命名了集合,关联二元运算和中性元素 - 宾果游戏!你有一个幺半群!关于
有点混乱。您可以在已经定义了关联操作,但我认为没有明确的标识元素定义
String
上选择不同的关联操作及其对应的中性元素,并定义各种幺半群。连接不像某种正统的#34;关于String
的关联操作来定义monoid,可能是最明显的。
如果你将一张桌子定义为&#34;它有四条腿和一个平坦的水平表面&#34;,那么符合这个定义的任何东西都是一张桌子,无论它是什么材料? #39;制造和其他可变特征。现在,你什么时候需要&#34;认证&#34;它显然是一张桌子?只有当你需要使用它的&#34; table-properties&#34;时,如果要出售它并宣传你可以放置它并且它们不会掉下来,因为表面保证< em> flat 和 horizontal 。
对不起,如果这个例子有点愚蠢,我在这种类比中并不是很好。我希望它仍然有用。
现在关于实例化提到的#34;理论Monoid
类型&#34;。这些类型通常称为type class。既不存在这种类型本身(可以通过各种方式定义),也不需要将其实例称为将三元组(String, (_ ++ _), "")
称为幺半群,并将原因称为幺半群(即使用通用幺半群属性)。实际使用的是ad-hoc polymorphism。在Scala中,它完成了implicits。例如,可以定义多态函数
def fold[M](seq: Seq[M])(implicit m: Monoid[M]): M = seq match {
case Seq.empty => m.zero
case (h +: t) => m.op(h, fold(t))
}
然后,如果您的stringMonoid
值声明为implicit val
,则只要您使用fold[String]
且stringMonoid
在范围内,它就会使用其zero
和{内部{1}}相同的op
定义适用于fold
的其他实例。
另一个主题是当您有多个Monoid[...]
实例时会发生什么。阅读Where does Scala look for implicits?。
答案 2 :(得分:1)
要回答你的问题,首先我们需要回答另一个问题:什么是幺半群?人们可以从至少两个不同的角度来看待它:
重要但是不要混合这两个观点。类别理论是数学的一种形式,因此类别理论中的幺半群属于Platonic Forms的领域,而在Scala编程语言中,根据Platonic realism,它是一个特殊的。
Scala中的幺半群只是一个理想的柏拉图式幺半群的反映。但我们可以自由选择我们如何进行这种反射,因为无论如何,特殊仅仅是形式的近似。这种反思是基于媒介的表达能力,其中发生了这种反射(在我们的例子中是Scala编程语言)。
Monoid要求我们使用&#34; identity&#34;来指定给定类型的唯一实例。概念。我认为唯一标识某种类型实例的最佳方法是使用单例类型,因为与值相反的类型保证是唯一的。由于Scala没有正确的单例类型(请参阅here进行讨论),因此无法在Scala中清楚地表达monoid Form,这可能是您提出问题的地方。
Scala对这个问题的回答是类型类。斯卡拉说:&#34;好吧,我不能将空字符串表示为一种独特的类型(在总和数字下也不能表示0,等等),所以我根本不会在那个级别上操作。相反,让我相信你有一个字符串的monoid类型类,我会让你在适当的时候使用它。&#34;这是一种非常实用的方法,但不是很纯粹。单例类型允许一个空字符串表示为唯一类型,字符串monoid表示这样(警告,这不会编译):
// Identity typeclass
trait Identity[T] {
type Repr
val identity: Repr
}
object Identity {
implicit val stringIdentity = new Identity {
type I = "" // can't do this without singleton types support
val identity = ""
}
}
trait Monoid[A] {
def op(a1: A, a2: A): A
def zero: Identity[A]#Repr
}
object Monoid {
implicit def stringMonoid[I](implicit
stringIdentity: Identity[String] { type Repr = I }) = new Monoid[String] {
def op(a1: String, a2: String): String = a1 + a2
def zero: I = stringIdentity.identity
}
}
我认为使用shapeless可能会非常接近这一点,但我还没有尝试过。
总而言之,从Scala语言的角度来看,monoid意味着有一个monoid类型类的实例,因为我们实现者选择将理想的monoid反映到Scala的现实中。你的问题将理想的monoid与现实世界的Scala monoid混合在一起,因此很难制定和回答。 Scala中缺少单例类型迫使我们做出假设,就像空字符串是String类型的标识一样。对于单身类型,我们不必假设,但我们可以证明这个事实,并在monoid定义中使用这样的证据。
希望这有帮助。
答案 3 :(得分:0)
如果你要使用String(或RichString)方法实现,你只需要String(在JVM中实现,或在SDK中补充)作为一个合适的Monoid(例如RichString extends Monoid[String]
)直接从Monoid获取,因此您不必实现它们。也就是说,如果我们知道String是一个Monoid,那么它就拥有了所有的操作,而我们不必实现它们。这种情况有两个问题:首先,在人们认为String是monoid之前很久就在JVM中实现了String,因此所有有用的方法都直接写入String或StringBuilder。其次,Monoid可能没有非常有用的方法。