2

From the following example, I think it is correct to say that String defines a monoid under the concatenation operation since it is an associative binary operation and String happens to have an identity element which is an empty string "".

scala> ("" + "Jane") + "Doe" ==  "" + ("Jane" + "Doe")
res0: Boolean = true

From the various texts I have been reading on the subject lately, it seems that the correct use of the term monoid is that the monoid is actually a combination of both the type (in this case String) and an instance of some monoid type which defines the operation and identity element.

For example, here is a theoretical Monoid type and a concrete instance of it as seems to be commonly defined in various books/articles:-

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

I know that we do not need trait Monoid[A] nor stringMonoid to be defined in the core (Scala, or rather Java) library to support my REPL output and that the example is just a tool to understand the abstract concept of a monoid.

My issue (and I am very possibly thinking about it way too much) is the purist definition. I know that the underlying java.lang.String (or rather StringBuilder) already defines the associative operation, but I do not think that there is an explicit definition of the identity element (in this case just an empty string "") defined anywhere.

Question:-

Is String a monoid under the concatenation operation implicitly, just because we happen to know that using an empty string "" provides the identity? Or is it that an explicit definition of the identity element for a type is unnecessary for it to be classed as a monoid (under a particular associative binary operation).

jacks
  • 4,183
  • 20
  • 29
  • Does `Int` form a `Monoid` under `addition` ? Yes it does, but there is no expliciotly defined `0` in Scala or in any other language. – sarveshseri Nov 02 '16 at 06:23
  • @SarveshKumarSingh, no `Int` doesn't form a Monoid under addition by itself, it is a monoid considered together with addition and `0` as the neutral element. – laughedelic Nov 02 '16 at 12:29
  • @laughedelic Not `Int` (Actually even `Int` does). I wanted `Integers`. And `Integers` form a `Monoid` under addition. – sarveshseri Nov 02 '16 at 12:52
  • @SarveshKumarSingh, my point is about neutral element, not the type/set. A set with binary associative operation but without a defined neutral element **is not** a _monoid_, it's called a [_semigroup_](https://en.wikipedia.org/wiki/Semigroup). Check the answers below, please. – laughedelic Nov 02 '16 at 12:57
  • @laughedelic Ok. Now I get it, we are talking about two different definitions. In `abstract algebra` `Monoid` is just `(S, +)` (given associativity, closure and existence of identity) where as in `category theory` it is the triplet `(S, +, I)`. – sarveshseri Nov 02 '16 at 13:20

4 Answers4

4

You may be overthinking this a little. A monoid is an abstract concept that exists outside of programming and type systems (a la category theory). Consider the definition:

A monoid is a set that is closed under an associative binary operation and has an identity element.

You have identified the String type to be a closed set with an associative binary operation (concatenation) and identity element (the empty string). The language may not explicitly tell you what the identity element is, but that doesn't mean that it doesn't exist (we know it does). The String type with the binary operation of concatenation is most certainly a monoid because you can prove that it meets all of the aforementioned properties.

Creating a Monoid type class is merely for our convenience when it comes to working with generic data structures that operate with monoids. Regardless of whether or not the programming language (or some other library) explicitly spells out what sets with what binary operations and what identities construct monoids, the monoids can still exist.

It is important to note that the String type on its own does not constitute a monoid, as it must exist with said binary operation, etc. It may be possible that another monoid exists using the same set, but a different binary operation.

Michael Zajac
  • 53,182
  • 7
  • 105
  • 130
2

I think that you already understand the concept right and indeed "think too much" about it (;

A monoid is a triplet as they say in mathematics: a set (think of a type with its values), an associative binary operator on it and a neutral element. You define such triple — you define a monoid. So the answer to

Is String a monoid under the concatenation operation implicitly, just because we happen to know that using an empty string "" provides the identity?

is simply yes. You named the set, the associative binary operation and the neutral element — bingo! You got a monoid! About

already defines the associative operation, but I do not think that there is an explicit definition of the identity element

there's a bit of confusion. You can choose different associative operations on String with their corresponding neutral elements and define various monoids. Concatenation is not like some kind of "orthodox" associative operation on String to define monoid, just, probably, the most obvious.

If you define a table as something "with four legs and a flat horizontal surface on them", then anything that fits this definition is a table, regardless of the material it's made and other variable characteristics. Now, when do you need to "certify" it explicitly is a table? Only when you need to use its "table-properties", say if to sell it and advertise that you can put things on it and they won't fall off, because the surface is guaranteed to be flat and horizontal.

Sorry, if the example is kind of stupid, I'm not very good in such analogies. I hope it is still helpful.


Now about instantiating the mentioned "theoretical Monoid type". Such types are usually called a type class. Neither existence of such type itself (which can be defined in various ways), nor its instance are necessary to call the triple (String, (_ ++ _), "") a monoid and reason about it as a monoid (i.e. use general monoid properties). What it is actually used for is ad-hoc polymorphism. In Scala it is done with implicits. One can, for example, define a polymorphic function

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

Then if your stringMonoid value is declared as implicit val, whenever you use fold[String] and stringMonoid is in scope, it will use its zero and op inside. Same fold definition will work for other instances of Monoid[...].

Another topic is what happens when you have several instances of Monoid[String]. Read Where does Scala look for implicits?.

Community
  • 1
  • 1
laughedelic
  • 5,499
  • 1
  • 24
  • 34
  • 1
    Very useful - thanks. Since I'm relatively new to FP and my maths is not overly strong I find I get caught up on some of the details of the theories. – jacks Nov 02 '16 at 09:37
  • I'm glad I could help. It's a good practice to ask questions in such cases. – laughedelic Nov 02 '16 at 12:26
1

To answer your question, first we need to answer another one: what is a monoid? One can look at it from at least 2 different perspectives:

  1. Category theory perspective,
  2. Scala programming language perspective.

It is important though to not mix these 2 perspectives. Category theory is a form of mathematics, and the monoid in category theory therefore belongs to the domain of Platonic Forms, while in Scala programming language, according to Platonic realism, it is a Particular.

So monoid in Scala is a mere reflection of an ideal Platonic monoid. But we are free to choose how exactly we do this reflection, because anyways the Particular is a mere approximation of a Form. This reflection is based on the expressive powers of the medium, in which this reflection happens (Scala programming language in our case).

Monoid requires us to designate a unique instance of a given type with the "identity" notion. I'd argue that the best way to uniquely identify an instance of some type is to use a singleton type, because types, as opposed to values, are guaranteed to be unique. Since Scala doesn't have the singleton types proper (see here for discussion), it's impossible to clearly express monoid Form in Scala, and this is probably where you question stems from.

The Scala's answer to this problem is the typeclasses. Scala says: "Ok, I can't express the empty string as an unique type (neither 0 as identity of numbers under sum, etc.), so I won't operate on that level at all. Instead, convince me you have a monoid typeclass for String, and I'll let you use it where appropriate." This is a very practical approach, but not very pure. Having singleton types would allow one to express an empty string as a unique type, and the string monoid like this (warning, this doesn't compile):

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

I think it would be possible to get pretty close to this using shapeless, but I haven't tried.

To summarize, from Scala language perspective, having monoid means having an instance of monoid typeclass, because so we, the implementers, choose to reflect the ideal monoid into Scala's reality. Your question mixes ideal monoid with real-world Scala monoid, and therefore it's so hard to formulate and answer. The lack of singleton types in Scala forces us to make assumptions, like that the empty string is the identity for the String type. With singleton types, we don't have to assume, but we can prove this fact, and use such proof in the monoid definition.

Hope this helps.

Community
  • 1
  • 1
Haspemulator
  • 10,462
  • 9
  • 46
  • 70
0

You would only need String (as implemented in the JVM, or as supplemented in the SDK) to be a proper Monoid (e.g. RichString extends Monoid[String]) if you were going to have String (or RichString) method implementations that you take directly from Monoid, so you do not have to implement them. That is, if we know that a String is a Monoid, then it has all its operations without us having to implement them. There are two problems with this scenario: first, String was implemented in the JVM a long time before anyone thought to recognize that String is a monoid, so all useful methods have been written directly into String, or StringBuilder. Second, Monoid probably does not have terribly useful methods anyway.

radumanolescu
  • 3,607
  • 1
  • 20
  • 30