> "lo" <> "fa"
"lofa"
> 1 <> 2
Error found:
in module $PSCI
at :1:1 - 1:7 (line 1, column 1 - line 1, column 7)
No type class instance was found for
Data.Semigroup.Semigroup Int
Is this for purely technical reasons as there would have to be at least 2 Semigroup
instances (for addition and multiplication), but type classes cannot be implemented multiple times for the same type?
Even if it could be done using instance chaining, one would have to create an instance of the entire "stack" of algebraic abstractions (Monoid
, Group
, Commutative
) for each operation, right?
In contrast, Semiring
is conveniently defined as a set with 2 binary operations1, which fits the bill perfectly:
A semiring (
S
,+
,•
) is a setS
with two binary compositions such that (S
,+
) and (S
,•
) are semigroups and distributes over+
.
source, pdf
So, technically, Int
is a semigroup under addition (a commutative monoid even), but PureScript is not capable to "infer" that Semigroup
's append
(<>
) and Semiring
's add
(+
), in effect, do the same thing, but, for obvious reasons, cannot be used interchangeably.2
Then again, I'm quite new to pure functional programming, PureScript, and abstract algebra, so my statements above may not make much sense - and thanks in advance for dispelling my confusion!
[1]: Although, nLab states that "mathematicians disagree on the definition of a semiring"...
[2]: Are there programming languages where this level of granularity is achieved? (That is, provided if this is even a thing and if I didn't totally misinterpret these mathematical ideas.)
Would you expect 1 <> 2
to evaluate to 3
or to 2
? Why?
I must say, I'm not sure I understood your elaboration correctly: in one place you are assuming that the Semiring Int
instance would be implemented in terms of addition, but in another place you admit that there are two possible ways to implement it - addition and multiplication.
But it's not, as you say, "purely technical reasons". The ambiguity is fundamental. Assuming there were no technical reasons of any kind and you could implement whatever you wanted, what should 1 <> 2
evaluate to? Should it be 3
or 2
?
There is no right answer. There isn't even an answer that would be "reasonable most of the time", like with strings. Whether you want addition or multiplication depends on the context.
And that is why there is no one standard implementation to rule them all. Sure, the standard library could just pick one, but then it would lead to subtle bugs for people who assumed the other way, or, at best, to angry questions and complaints.
But there is a way to pick one or the other explicitly. Meet newtype Additive
and its evil twin Multiplicative
. They would wrap your Int
(or any other Semiring
actually) and provide a Semigroup
instance in additive or multiplicative sense respectively:
> import Data.Monoid.Additive
> Additive 1 <> Additive 2
(Additive 3)
> import Data.Monoid.Multiplicative
> Multiplicative 1 <> Multiplicative 2
(Multiplicative 2)
And you can, of course, use that in any context where Monoid
-ish classes are expected:
> fold (Additive <$> [1,2,3,4])
(Additive 10)
> fold (Multiplicative <$> [1,2,3,4])
(Multiplicative 24)
And while we're on the subject, you might be interested in some other similar newtypes from the Data.Monoid.*
namespaces, such as Conj
and Disj
, solving a similar problem for booleans, Dual
, which reverses direction, or Endo
for composing functions.
And a special shout out for Down
, working tirelessly to enable reverse sorting.