purescript / purescript-prelude

The PureScript Prelude

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Separate Semiring into additive and multiplicative parts

JamieBallingall opened this issue · comments

Currently, Semiring defines add, zero, mul and one all in one place. This is inconvenient if one wishes to define addition without multiplication (e.g., vectors) or addition and multiplication without zero (e.g., the natural numbers starting at 1).

Could we separate out four typeclasses:

  • AdditiveSemigroup defines a single method (add) that is associative and commutative
  • AdditiveMonoid inherits AdditiveSemigroup and adds zero and laws defining it as the additive identity
  • MultiplicativeSemigroup defines a single method (mul) that is associative but not necessarily commutative
  • MultiplicativeMonoid inherits MultiplicativeSemigroup and adds one and law defining it as the multiplicative identity

Then Semiring inherits AdditiveMonoid and MultiplicativeMonoid and adds laws regarding distribution of multiplication over addition and absorption.

Also, we might introduce a Subtractive class (with negate and sub), which would itself be a commutative group, and have Ring inherit Semiring and Subtractive.

This approach is inspired by the Haskell library NumHask but don't think that we need to follow it too closely. In particular, I don't see a need to change the superclasses of Field. But following NumHask, I think it is reasonable and fairly common practice to hardwire commutativity into addition but not multiplication.

This would be a breaking change to any code that is defining it's own instances of Semiring or Ring but shouldn't affect any code that simply uses the instances defined in Prelude.

This is an interesting idea.

Does this solve a problem we commonly have? I can't really think of a case where my code would have benefited from this - I'd just write it over Semigroup or Monoid, and then use the Additive or Multiplicative newtypes to choose the operations I wanted when passing in (semi)ring-like types.

At the risk of being glib, it solves a problem I commonly have. That problem is only a notational one -- I'm forced to use <> or ^+^ or something when I would like to have used + or *.

The answer may be that I should write a custom Prelude. That is essentially the approach that NumHask has taken with Haskell. But Purescript's numeric heirarchy is already much cleaner than Haskell's and we are tantilizingly close to something near-perfect.

Let me provide two examples where Semiring squatting on so much basic syntax forces us to use non-standard and slightly harder to read/understand syntax.

Standard mathematical notation for vector spaces uses + for vector-vector addition and * (well, usually just juxtaposition) for scalar-vector multiplication. Having * for scalar-vector multiplication would be nightmare but it would be nice to have + for vector-vector addition since it obeys all the laws that Semiring specifies it should obey (it's a commutative group)

There seem to be three approaches to this:

  1. class (Semiring v, Field a) <= VectorSpace v a
  2. class (Monoid v, Field a) <= VectorSpace v a
  3. Define a new addition operator (say ^+^) as part of VectorSpace

I haven't see anyone use approach 1 because it forces anyone instantiating VectorSpace to define mul and one, which is hard even for concrete instances like newtype Vec a = Array a.

My objection to 2 and 3 is just a notation issue. Using <> or ^+^ just bogs down expressions that should be simple. Similar issues arise when trying to define matrices.

A second example are the natural numbers starting at one ("N1"). These crop up naturally as the length of non-empty collections and knowing that a value is at least 1 is useful in avoiding division by zero errors when, for example, computing the average. Here defining a semiring is out of the question -- zero is explicitly excluded from the definition of N1. We could define a monoid, but for which operation? Addition or multiplication? So we fall back to custom operators ^+^ and ^*^ possibly associated with custom typeclasses.

My own code contains a class Semrng which defines addition and multiplication (with ^+^ and ^*^ style operators) and laws about multiplication distributing over addition.

The joke, btw, is that a Semrng is a Semiring without the the two identity elements in the same ways that a "Rng" is a Ring without a multiplicative identity. A Rng is another algebraic structure defined on Wikipedia which is clunky to define under the current definition of Semiring but I haven't had need of it myself.

Unless I have misunderstood, Additive and Multiplicative don't help with these kinds of problems. If you have a Semiring and wish to use <> syntax then they are great. But if you have a Semigroup and wish to use + then I don't see how they help.

One slight change to my original proposal: The name Subtractive was inspired by NumHask but AdditiveGroup would be more consistent with the rest of the names in both this proposal and the rest of the Prelude. And perhaps Semrng isn't as silly as I first thought. Having something that introduces multiplication distributing over addition seperately from identities is worthwhile. Maybe we need a better name but Distributive is already taken.

Yeah, I think this Prelude isn't really the place for this then - I don't think there's a common enough need for what you're describing for the general PS user. I can't provide concrete evidence for that (or precisely define what a "general PS user" is even!) but this is the first time I'm aware of that something like this has been discussed in a place that the core team would be expected to see it, so if it were a common desire I imagine I would have seen it before.

I understand where you're coming from - I was once a proponent of the Prelude having a much finer grained and more expansive algebraic hierarchy, but eventually came around to the idea that something simpler is more appropriate for the Prelude. I spent ages trying to write something here to elaborate further but I don't seem to be able to put anything coherent together tonight! I think part of the difficulty with doing so relates to our lack of explicit criteria for the choices we make in the Prelude as it is, we just "know it when we see it", which is an annoying justification I know, but that's all I've got.

A thought on your Semrng class: could you not define it with the standard + and * operators if you're using your own prelude? It'd mean defining instances for Semrng for the all the types you would usually use Semiring for, or perhaps depending on the arrangement you could possibly use a catch-all instance chain for those even, but at least then you'd have the syntax you want for it within your project(s).

edit: this is just my opinion, I don't intend to shut down further discussion here!

I'm probably misunderstanding something here. It sounds like your goal (and perhaps only goal) is to override the plus``+ function, so that + can be used for multiple types below

1 + 1
someVector + anotherVector

Is that all? Or is there more to it than that? In other words, why would the below code not work for you?

module HasAdd where

class HasAdd a where
  add :: a -> a -> a
  
instance HasAdd Int where
  add = Prelude.add
  
instance HasAdd VectorSpace where
  plus = ...
  
infixl 6 add as +

and then

module Foo where

import Prelude hiding (add, (+))

import HasAdd (add, (+))

I think the answer here is a custom prelude or maybe even a library that reexports parts of the prelude but defines the numeric hierarchy differently. I think import Prelude hiding (add, (+) would become cumbersome but import NumericPrelude would be fine.

One sentiment I'd like to note is: "I don't think there's a common enough need for what you're describing for the general PS user". That's probably true but I want to ensure that it doesn't become self-fulfilling. I don't want people trying Purescript, writing some simple numerics code, discovering that something "simple" is hard or has odd syntax and concluding that Purescript isn't a good choice for numerics. There is a lot to do to ensure that Purescript is a nice place to do numerics and frankly, NumericPrelude is probably the place to do it.