Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial algebras. #341

Closed

Conversation

denisrosset
Copy link
Collaborator

Split Action into LeftAction and RightAction.
Renamed the Action syntax classes accordingly instead of GroupOps, PointOps.
Added PartialAction implementation, with both Option-like and PartialFunction-like semantics.
Added implementation of Semigroupoid, PartialMonoid, Groupoid.
Added syntax for partial operations, with both Option-like and PartialFunction-like semantics.

Renamed the Action syntax classes accordingly instead of GroupOps, PointOps.
Added PartialAction implementation, with both Option-like and PartialFunction-like semantics.
Added implementation of Semigroupoid, PartialMonoid, Groupoid.
Added syntax for partial operations, with both Option-like and PartialFunction-like semantics.
@denisrosset
Copy link
Collaborator Author

Note: I haven't yet ported the tests (especially the relevant laws) to Spire.

In particular, I'd like a review of the naming scheme (Semigroupoid, PartialMonoid, Groupoid) from people who already have a feet in category theory. In particular, even if a PartialMonoid looks very much like a Category, I want to avoid the C-word who has already countless usages.

Last note:

As discussed in #337, even the goal is not to define in Spire all algebraic structures known under the sun, the partial algebraic structures are needed because Group should inherit from Groupoid, and not the other way around --- which prevents moving Groupoid into an extra library.

Usage examples:

  • partial operations are defined on a Seq[A] using the algebraic structure of A; e.g. Semigroupoid[Seq[A]] is derived from Semigroup[A] for sequence of compatible sizes.
  • (my use case) transformations of objects that can be composed, but where several classes of objects exist, and composition of transformations have to match source and target object classes.

@denisrosset denisrosset changed the title Split Action into LeftAction and RightAction. Partial algebras. Dec 15, 2014
@denisrosset
Copy link
Collaborator Author

Laws have been defined for partial algebras and actions.

All these laws are under the existing GroupLaws and ActionLaws hierarchy, to enable mix'n'match: i.e. PartialAction[P, G] with scalar Semigroupoid[G].

@non
Copy link
Contributor

non commented Dec 16, 2014

Awesome!

I'm sorry I haven't had more time to comment on all this work -- there have been a lot of things going on. It seems promising, and I will dig in and give concrete feedback as soon as I can.

Thanks for working on this.

@denisrosset
Copy link
Collaborator Author

@non: no worries, I have my own local snapshot for now.

To advance the discussion, do you have in mind concrete examples of partial algebras/actions, that could be provided in Spire, either in spire.std or spire.optional, or in test cases ?

For now, I'm thinking of Semigroupoid[Seq[A]], PartialMonoid[Seq[A]], Groupoid[Seq[A]] given A: Semigroup/Monoid/Group, where the partial operation is defined only on sequences with compatible sizes.

There is also the groupoid of invertible matrices, where matrices of the same size are compatible, but we probably do not want to go down that path (i.e. define partial rings, and so on).

There is also the pedagogical example of the 15 puzzle:

https://cornellmath.wordpress.com/2008/01/27/puzzles-groups-and-groupoids/

For partial actions, there is of course the action of a permutation on a Seq, where the Seq has to have a compatible size.

…eption

Replace some sys.errors with proper exceptions.
@non
Copy link
Contributor

non commented Dec 22, 2014

Hi @denisrosset,

I'm going to get an 0.9.0 release out, (mostly to get your interval bug fixes and improvements released) and then my next plan is to get this stuff merged. I'm hoping to do it this week while I'm on vacation.

There is still a lot of work to be done making the documentation
more complete, up-to-date, and useful. But this commit is a start,
and adds the credits and changes for the newest relese.
@denisrosset
Copy link
Collaborator Author

Hi @non,

I'd like to release my alasc library for finite groups, and my project depends on Groupoid and PartialAction.

Any chance you'd accept a reduced version of this PR for 0.9.0? otherwise, of course, I can live with a local SNAPSHOT.

I'm thinking of:

  • PartialAction, with the method partialActl/r only, without attached symbolic operators (they should be discussed)
  • Groupoid with partialOp and inverse only

and Action extending PartialAction, implementing partialActl/r using actl/r, Group extending Groupoid, implementing partialOp using op.

Both objects (partial actions and groupoids) have unambiguous names and clear laws stated in the mathematical litterature --- this is not the case for Semigroupoid and PartialMonoid.

This would imply minimal changes to the syntax and laws packages.

We could then discuss for a future release:

  • PartialFunction-like methods for speed
  • symbolic operators
  • names for Semigroupoid PartialMonoid, as they are not standardized
  • Action split into LeftAction and RightAction (important for actions defined using semigroups or monoids)
  • ...

What do you think ?

@non
Copy link
Contributor

non commented Dec 22, 2014

So, I did already release 0.9.0. But I am willing to merge this quickly and do an 0.9.1 release too. I think we can get it out before 2015.

@denisrosset
Copy link
Collaborator Author

OK, thanks! I see that 0.9.0 is out, so there is no hurry.

Let's identify the parts that are uncontroversial from the ones that require discussion, and I'll be happy to split to PR in several pieces. I'd like to see a 0.9.1 release after the steps 1 (and 2?) below, because they introduce new super traits for Action and Group:

Step 1: We can discuss quickly (there is not much wiggle room in implementation/naming these):

  • the LeftAction/RightAction split
  • PartialAction methods (partialAct#, isAct#Defined, forceAct#)
  • Groupoid and Groupoid methods (partialOp, isOpDefined, forceOp, leftId, rightId)
  • default implementation strategy (the easy-to-understand partial### is abstract, and the optimized PartialFunction-like interface has to be overridden for speed explicitly)

Step 2: Check that the following are meaningful:

  • symbolic operators for PartialAction
  • symbolic operators for Groupoid

Step 3: Brainstorm around

  • the naming scheme for Semigroupoid, PartialMonoid (BTW, I'm not using them at the moment, they could be dropped)
  • if we need to have Groupoids with a base (in the category-theoretic sense)
  • ...

@non
Copy link
Contributor

non commented Dec 22, 2014

I think the design (point #1) seems good.

The biggest questions I have are around what symbolic operators we should use. If we try to hit all possible combinations I worry we will "use up" the design space, and also end up with really cumbersome symbols.

@non
Copy link
Contributor

non commented Dec 22, 2014

As far as which instances Spire should provide, I think we can be minimal here. One of the advantages of a shared type class is that it lets other libraries decide which instances are meaningful and implement them. They can always pass the types and instances back upstream if they are very useful generally.

@non
Copy link
Contributor

non commented Dec 22, 2014

Also, as we get more and more algebras, I'm trying to do a better job of partitioning the spire.algebra package.

With that in mind, I think it would make sense to put these into spire.algebra.partial. What do you think?

@non
Copy link
Contributor

non commented Dec 22, 2014

Operators:

Right now it looks like we will be adding:

algebra operator result description
Semigroupoid[A] `A + ? A`
`A + ! A`
A ?+? A Boolean test if op is defined.
Groupoid[A] `A - ? A`
`A - ! A`
`A - ? A`
LeftPartialAction[P, G] `G ? + > P`
`G ! + > P`
`G ?+ > P` Boolean
RightPartialAction[P, G] `P < + ? G`
`P < + ! G`
`P < +? G` Boolean

What do you think @tixxit?

@denisrosset
Copy link
Collaborator Author

Regarding: error if op undefined (unsafe) should be read as undefined result (including throwing an exception) if op undefined (unsafe), just as with PartialFunction.

@denisrosset
Copy link
Collaborator Author

Let's put them in spire.algebra.partial, actually they should be here to provide the Group, Semigroup parent traits.

@non
Copy link
Contributor

non commented Dec 22, 2014

Okay, right, that totally makes sense. Given that usage, how would you feel about just using |+| (i.e. op) in the unsafe cases? It would cut down on the extra operators needed, and make its relationship to the total algebras clearer.

@denisrosset
Copy link
Collaborator Author

I'm ok with merging |+|! and |+|.

Then actl/r would be defined in the Semigroupoid trait as partialActl/r(...).getOrElse(sys.error('incompatible')).

@non
Copy link
Contributor

non commented Dec 22, 2014

So in the other bug you said you were favoring only definining .opIsDefined and .op. However, for calculations which are expensive to perform (and where you don't know if the result is "defined" until the end) this will actually be a bad performance story, right?

If we introduce something like Nullbox into spire.util my instinct is to consider going the other direction, and making .partialOp the primary thing, with default .op and .isDefined methods which can be overridden.

What do you think?

@denisrosset
Copy link
Collaborator Author

I agree completely with you for spire.

Still, I'm favoring methods that are not bound to a particular Option-like implementation in the non/algebra package.

How could we reconcile the two ? should we have a minimalistic partial algebra trait in non/algebra, and a more complete one Spire, with an isInstanceOf check in Spire's extended syntax ?

@denisrosset
Copy link
Collaborator Author

I'm writing a proposal right in this PR (for simplicity, I will not submit a PR to algebra).

@non
Copy link
Contributor

non commented Dec 28, 2014

That sounds good. Thanks again for bearing with us on all of this.

@denisrosset
Copy link
Collaborator Author

The split is done; in the code, the traits that do not use Nullbox should go in algebra.

For consistency, I redefined the partial operators to be |+|? |+|?? |-|? |-|?? <|+|? <|+|?? ?|+|> ??|+|>, with the single ? indicating a partial operation with a Nullbox result, and the double ?? indicating an XXXisDefined method.

I'm still undecided for the implementation of the Nullbox... traits. Overall, I'm probably going for:

  • a PartialFunction-like trait (for this example: Semigroupoid) that should be shared between all libraries,
  • a NullboxSemigroupoid trait that defines only the partialXXX methods,
  • a SpireSemigroupoid trait that extends both Semigroupoid and NullboxSemigroupoid.

The only drawback is the explosion of (specialized) classes. The SpireSemigroupoid-like classes are there for convenience and can be ommited.

Then, we would have the following operators:

  1. PartialFunction-like syntax such as |+| and |+|?? for the plain partial traits,
  2. Nullbox syntax for single qmark operators |+|? with a Nullbox result,
  3. an implicit conversion from the plain partial trait to the Nullbox enrichment.

Instead of an implicit conversion in 3., we could also implement a special macro with an isInstance test and optimized code paths.

@denisrosset
Copy link
Collaborator Author

@denisrosset
Copy link
Collaborator Author

Actually, should we be more consistent in naming the partial methods, by having both actlIsDefined and actlPartial with the original method name first ?

@denisrosset
Copy link
Collaborator Author

Tests with YourKit Java Profiler indicate that the following code (when a generic Groupoid is used instead of a specialized NullboxGroupoid)

import spire.algebra._
import spire.syntax.all._

implicit object SeqIntGroupoid extends Groupoid[Seq[Int]] {
  def inverse(a: Seq[Int]) = a.map(-_)
  def opIsDefined(f: Seq[Int], g: Seq[Int]) = f.size == g.size
  def op(f: Seq[Int], g: Seq[Int]) = Seq.tabulate(f.size)(i => f(i) + g(i))
}

for (i <- 1 to 100000) (Seq(1,2,3) |+|? Seq(4,5,6)).getOrElseFast(Seq.empty[Int])

does not allocate Nullbox objects, but allocates one implicitly converted NullboxSemigroupoid per iteration, whose size is 16 bytes (the JVM does not seem to remove the allocation). Note that removing the getOrElseFast method leads to the explicit instantiation of Nullbox, probably because then a Nullbox is the type of the expression computed by the for loop iteration.

@denisrosset
Copy link
Collaborator Author

Note that I'm triggering a bug in the 2.10 compiler with the enrichment, because of the value class return type; the CI will be not passing for 2.10.

This is probably a variant of https://issues.scala-lang.org/browse/SI-8702 .

@denisrosset
Copy link
Collaborator Author

@non : I see that a lot of work is being done on non/algebra, I will have a look at it.

This can wait until the work there is finished.

@non
Copy link
Contributor

non commented Jan 13, 2015

Yeah sorry I haven't been more responsive. I'm definitely giving it some thought.

@non
Copy link
Contributor

non commented Jan 13, 2015

One note to add: if we are willing to not require Group[A] to inherit Groupoid[A], but to support an implicit wrapper instead, then it will be very easy to add these to Spire first and wait and see what happens in algebra, since we won't be required to have them there.

What do you think about this? @tixxit?

@denisrosset
Copy link
Collaborator Author

We could also use inheritance the other way around, e.g. have partial algebras inherit the regular ones --- this is actually what is done in PartialFunction[A, B] extends (A => B).

We would thus remove the Nullbox / non-Nullbox distinction and use Nullbox everywhere.

There would be an implicit wrapper for the regular algebras used partially, so a small overhead --- except if we can write a macro that generates a .isInstanceOf[Partial...] stub.

I am waiting for @tixxit before making a move.

@non
Copy link
Contributor

non commented Jan 13, 2015

I would prefer to avoid inheriting in that direction -- the PartialFunction vs Function relationship has been causing a lot of pain for Scala users for some time now. Also, I would rather not use Nullbox[A] except in cases where we really need it, since in some sense it "de-specializes" primitives.

@denisrosset
Copy link
Collaborator Author

Note: this is superseded by #353, #356 and #357.

Please have a look at #356 first.

@denisrosset denisrosset mentioned this pull request Jan 29, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants