Skip to content

Commit fdbd059

Browse files
adding neutral singleton value and respective monoid semantics
1 parent 5452276 commit fdbd059

File tree

8 files changed

+53
-42
lines changed

8 files changed

+53
-42
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.1.0] - 2021-07-23
11+
### Added
12+
- `neutral` can be used as generic `neutral` value. With this every Semigroup is automatically a Monoid.
13+
- `neutral(type)` now defaults to returning `neutral` instead of throwing a not-implemented-error.
14+
15+
### Changed
16+
- `pure(Writer, value)` now initializes the accumulator to `TypeClasses.neutral` instead of `Option()`, making it strictly more general with regard to `TypeClasses.combine`.
17+
18+
### Removed
19+
- `reduce_monoid`, `foldl_monoid` and `foldr_monoid` can now only be called as `reduce_monoid(iterable; [init])`. The older variant `reduce_monoid(combine_function, iterable; [init])` was a left over from previous thrown-away iterations.
20+
1021
## [1.0.0] - 2021-07-16
1122
### Added
1223
- extensive documentation is ready

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "TypeClasses"
22
uuid = "addcc920-e0cf-11e8-30b7-0fb08706b574"
33
authors = ["Stephan Sahm <[email protected]> and contributors"]
4-
version = "1.0.0"
4+
version = "1.1.0"
55

66
[deps]
77
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"

docs/src/manual-DataTypes.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -660,14 +660,14 @@ julia> @syntax_flatmap begin
660660
Writer{String, Tuple{Int64, Int64}}("first.second.", (5, 25))
661661
```
662662
663-
In case you only have a Semigroup, just wrap it into `Option`, the default `TypeClasses.pure` implementation for writer will use `Option()` internally.
663+
In case you only have a Semigroup, no problem, as the default `TypeClasses.pure` implementation for writer will use `neutral` as the accumulator, which combines with everything.
664664
```jldoctest
665665
julia> @syntax_flatmap begin
666666
a = pure(Writer, 5)
667-
Writer(Option("hi"))
667+
Writer("hi")
668668
@pure a
669669
end
670-
Writer{Identity{String}, Int64}(Identity("hi"), 5)
670+
Writer{String, Int64}("hi", 5)
671671
```
672672
673673
### Monoid/Alternative
@@ -685,7 +685,7 @@ julia> Writer("hello.") ⊕ Writer("world.") # the single argument constructor
685685
Writer{String, Nothing}("hello.world.", nothing)
686686
```
687687
688-
We don't implement `orelse`, as it is commonly meant on container level, but there is no obvious failure semantics here.
688+
We do not implement `orelse`, as it is commonly meant on container level, but there is no obvious failure semantics here.
689689
690690
691691
### FlipTypes

docs/src/manual-TypeClasses.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ Monoid, Alternative | `TypeClasses.neutral` |
124124
Monoid, Semigroup | `TypeClasses.combine` | alias `` (\oplus), `reduce_monoid`, `foldr_monoid`, `foldl_monoid`
125125
Alternative | `TypeClasses.orelse` | alias `` (\oslash)
126126

127-
A **Semigroup** just supports `combine`, a **Monoid** in addition supports `neutral`.
127+
A **Semigroup** just supports `combine`, a **Monoid** in addition supports `neutral`. We define the generic neutral element `neutral` which is neutral to everything, hence every Semigroup is actually a Monoid in Julia. Hence `TypeClasses.neutral` is both a function which returns the neutral element (defaulting to `neutral`), as well as the generic neutral element itself.
128128

129129
Sometimes, the type itself has an obvious way of combining multiple values, like for `String` or `Vector`. Other times, the `combine` is forwarded to inner elements in case it is needed.
130130

src/TypeClasses/MonoidAlternative.jl

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
# ==================
33

44
"""
5-
neutral(::Type{T})::T
5+
neutral
6+
neutral(::Type)
7+
neutral(_default_return_value) = neutral
68
7-
Neutral element for `⊕`, also called "identity element".
9+
Neutral element for `⊕`, also called "identity element". `neutral` is a function which can give you
10+
the neutral element for a concrete type, or alternatively you can use it as a singleton value which combines with everything.
11+
12+
By default `neutral(type)` will return the generic `neutral` singleton. You can override it for your specific type to have a more specific neutral value.
813
914
We decided for name `neutral` according to https://en.wikipedia.org/wiki/Identity_element. Alternatives seem inappropriate
1015
- "identity" is already taken
1116
- "identity_element" seems to long
1217
- "I" is too ambiguous
1318
- "unit" seems ambiguous with physical units
1419
15-
# Following Laws should hold
20+
21+
Following Laws should hold
22+
--------------------------
1623
1724
Left Identity
1825
@@ -23,7 +30,7 @@ Right Identity
2330
⊕(t::T, neutral(T)) == t
2431
"""
2532
function neutral end
26-
neutral(T::Type) = error("neutral($T) not defined")
33+
neutral(T::Type) = neutral # we use the singleton type itself as the generic neutral value
2734
neutral(a) = neutral(typeof(a))
2835

2936

@@ -48,43 +55,33 @@ const ⊕ = combine
4855
combine(a, b, c, more...) = foldl(, more, init=(ab)c)
4956

5057

58+
# supporting `neutral` as generic neutral value
59+
combine(::typeof(neutral), b) = b
60+
combine(a, ::typeof(neutral)) = a
61+
combine(::typeof(neutral), ::typeof(neutral)) = neutral
5162

52-
struct _InitialValue end
5363

5464
for reduce [:reduce, :foldl, :foldr]
5565
reduce_monoid = Symbol(reduce, "_monoid")
5666
_reduce_monoid = Symbol("_", reduce_monoid)
5767

5868
@eval begin
5969
"""
60-
$($reduce_monoid)(itr; [init])
61-
$($reduce_monoid)(monoid_combine_function, itr; [init])
62-
63-
Combines all elements of `itr` using the initial element `init` if given and `combine`.
64-
If no `init` is given, `neutral` and `combine` is used instead.
65-
66-
If `monoid_combine_function` is given, it `neutral(monoid_combine_function)` is expected to return the corresponding `neutral` function
70+
$($reduce_monoid)(itr; init=TypeClasses.neutral)
71+
72+
Combines all elements of `itr` using the initial element `init` if given and `TypeClasses.combine`.
6773
"""
68-
function $reduce_monoid(itr; init = _InitialValue())
69-
$_reduce_monoid(TypeClasses.combine, TypeClasses.neutral, itr, init)
70-
end
71-
function $reduce_monoid(monoid_combine_function, itr; init = _InitialValue())
72-
$_reduce_monoid(monoid_combine_function, neutral(monoid_combine_function), itr, init)
73-
end
74-
function $_reduce_monoid(combine, neutral, itr, init)
75-
op(acc::_InitialValue, x) = x
76-
op(acc, x) = combine(acc, x)
74+
function $reduce_monoid(itr; init=neutral)
7775
# taken from Base._foldl_impl
7876

79-
# Unroll the while loop once; if init is known, the call to op may
80-
# be evaluated at compile time
77+
# Unroll the while loop once to hopefully infer the element type at compile time
8178
y = iterate_named(itr)
82-
isnothing(y) && return neutral(eltype(itr))
83-
v = op(init, y.value)
79+
isnothing(y) && return init
80+
v = combine(init, y.value)
8481
while true
8582
y = iterate_named(itr, y.state)
8683
isnothing(y) && break
87-
v = op(v, y.value)
84+
v = combine(v, y.value)
8885
end
8986
return v
9087
end

src/TypeInstances/Writer.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ end
2121
# We fall back to assume that the general writer uses Option values in order to have a neutral value
2222
# this should be okay, as it is canonical extension for any Semigroup to an Monoid
2323
function TypeClasses.pure(::Type{<:Writer}, a)
24-
Writer(Option(), a)
24+
Writer(neutral, a)
2525
end
2626

2727
# Writer always defines `combine` on `acc`

test/TypeClasses/MonoidAlternative.jl

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
# ---------------------------------------
33

44
TypeClasses.combine(a::Int, b::Int) = a + b
5-
TypeClasses.neutral(::Type{Int}) = 0
65

76
@test reduce_monoid([1,2,3,1]) == 7
87
@test foldl_monoid([1,2,3,4]) == 10
9-
@test foldr_monoid([1,2,3,100]) == 106
8+
@test foldr_monoid([1,2,3,100], init=3000) == 3106
109

1110

1211
# Test default Semigroup instance for String
@@ -15,11 +14,9 @@ TypeClasses.neutral(::Type{Int}) = 0
1514
@test reduce_monoid(["hi"," ","du"], init="!") == "!hi du"
1615

1716

18-
# Define and Test neutral for functions
19-
# -------------------------------------
17+
# Test `neutral` singleton value
18+
# ------------------------------
2019

21-
myfunction(a, b) = a * b
22-
TypeClasses.neutral(::Type{typeof(myfunction)}) = one
23-
@test reduce_monoid(myfunction, [1,2,3,4]) == 1*2*3*4
24-
25-
# TODO Test Alternative
20+
@test combine(neutral, :anything) == :anything
21+
@test combine("anything", neutral) == "anything"
22+
@test combine(neutral, neutral) == neutral

test/TypeInstances/Writer.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ end) == Writer("hi", 5)
4242
@pure a
4343
end) == Writer(Option("hi"), 5)
4444

45+
@test (@syntax_flatmap begin
46+
a = pure(Writer, 5)
47+
Writer("hi")
48+
@pure a
49+
end) == Writer("hi", 5)
50+
4551

4652
# FlipTypes
4753
# =========

0 commit comments

Comments
 (0)