juan_gandhi: (VP)
How many even numbers do you need so that their product is odd?

1) With integer numbers
2) With integer complex numbers
juan_gandhi: (VP)
Never saw this before:
- Syntactic Monoid
- "In computer science, more precisely in automata theory, a recognizable set of a monoid is a submonoid that can be mapped in a certain sense to some finite monoid through some morphism."
juan_gandhi: (VP)
Spent a couple of weeks trying to figure out what exactly is it. Looked it up in Wikipedia, in Scala, in Haskell, in popular articles. Now I think I know what it is; here's my vision. Please pardon my English and my cheap, simplified Math.

1. Type Class is an Algebraic Theory

An algebraic theory is a rather abstract notion. It consists of variables of one or more types, a collection of operations, and a collections of axioms.

1.1. Example
Monoid. Just one variable type; two operations: a binary operation and a nullary operation (that produces a neutral element), and axioms: associativity and neutral element neutrality. Integer numbers can be perceived as a monoid, say, using multiplication as the binary operation and 1 as the neutral element.

1.2. Example
Vector space over a field. We have two types of variables, vectors and scalars. Scalars can add, subtract, multiply, divide, have a zero, a one; vectors can add, subtract, have a zero, can be multiplied by a scalar. We can also throw in scalar product of two vectors.

2. But Can We Express A Theory As Trait (interface, in Java)?

On many occasions, yes. You can define a pure abstract trait (or, in Java, just an interface) that defines the operations, e.g. for a monoid:

trait Monoid {
  def neutral: Monoid
  def binOp(another: Monoid): Monoid
}


Notice, we do not specify any axioms. In languages like Scala, Haskell, Java, we cannot. It takes Agda to handle axioms.

We have a problem here that the if we try to introduce a specific kind of monoid, the result of binOp does not belong to that kind; so we have to be more careful and design our trait keeping in mind we are going to extend it, like this:

trait Monoid[Actual] {
  def neutral: Actual
  def binOp(another: Actual): Actual
}


Now we can define something like
 
class StringConcatMonoid(val s: String) extends Monoid[StringConcatMonoid] {
  def neutral = new StringConcatMonoid("")
  def binOp(another: StringConcatMonoid) = new StringConcatMonoid(s + another.s)
}


It works... but well, the new class is not exactly a String class, right? That's the problem, we cannot throw in the new functionality into String.

Suppose for a moment we could (we can do it in JavaScript ad libitum). What would we do with Ints then? What binary operation, addition? multiplication? min? max? There might be more.

Now let's disambiguate.

2 3. Model

When we defined a trait without implementation, we described a theory. When we started building specific implementations, we define a model for our theory. There may many models for the same theory; the same structure may be a model of a variety of theories. We have to be able to express this relationship.

In the example above we made StringConcatMonoid a model of Monoid theory. We were lucky, we had just one type; imagine we had more than one. Then there's no way to inherit anything. And we are still not happy that we cannot define binOp on Strings themselves; we look at Haskell, and seems like they do it easily.

In Haskell, models are called instances (of a type class); in C++0x (please excuse my misspelling) they are called models (and theories are called concepts).

In Haskell one can define
  class Monoid m where
    binOp   :: m -> m -> m
    neutral :: m


and model it with lists (strings are lists in Haskell):
instance Monoid [a] where
    binOp = (++)
    neutral = []


We can do it in Scala, kind of more verbosely.

object A {
  implicit object addMonoid extends Monoid [Int] {
    def binOp (x :Int,y :Int) = x+y
    def neutral = 0
  }
}
object B {
  implicit object multMonoid extends Monoid [Int] {
    def binOp (x :Int,y :Int) = x ∗ y
    def neutral = 1
  }
}
val test :(Int,Int,Int) = {
  {
    import A._
    println(binOp(2, 3))
  }
  {
    import B._
    println(binOp(2, 3))
  }
}


In one case we used one binOp, in another we used another. We can define fold that implicitly accepts an instance of Monoid, and provides the required operation, but that's beyond the topic of this talk.

3. Important Remark
We could probably start thinking, hmm, how about just parameterized types, what's the difference? Say, take List[T], is not it a type class? We have abstract operations, independent of the type T, and so it is also not a specific type, but a class of types, right?

Not exactly. It is a totally different thing, unfortunately written in the same style. Here we have a functor: given a type T, we produce another type, List[T], uniquely determined by the type T, and whose operations (almost) do not depend on what is T.
While we could make it into a type class, if the polymorphism here were ad-hoc, we normally do not.

4. That's Not It Yet

Next I'll show how we can restrict the scope of certain operations to certain subtypes, of the type that we pass as a parameter.

please correct me where I'm wrong

Profile

juan_gandhi: (Default)
Juan-Carlos Gandhi

June 2025

S M T W T F S
1 2345 6 7
8 9 10 11 121314
15161718192021
22232425262728
2930     

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 13th, 2025 11:59 pm
Powered by Dreamwidth Studios