TypeClasses Public API
Functor, Applicative, Monad
Foreach, using Base.foreach
TypeClasses.@syntax_foreach — Macro@syntax_foreach begin
# Vectors behaves like nested for loops within @syntax_foreach
a = [1, 2, 3]
b = [10, 20]
@pure a + b
end
# [[11, 21], [12, 22], [13, 23]]This is a variant of the monadic syntax which uses foreach for both maplike and flatmaplike. See Monadic.@monadic for more details.
Functor, using Base.map
TypeClasses.@syntax_map — Macro@syntax_map begin
# Vectors behave similar to nested for loops within @syntax_map
a = [1, 2, 3]
b = [10, 20]
@pure a + b
end
# [[11, 21], [12, 22], [13, 23]]This is a variant of the monadic syntax which uses map for both maplike and flatmaplike. See Monadic.@monadic for more details.
Applicative Core
TypeClasses.pure — Functionpure(T::Type, a)
wraps value a into container T
TypeClasses.ap — Functionap(f::F1, a::F2) -> F3
Apply function in container F1 to element in container F2, returning results in the same container F3. The default implementation uses flatmap and map, so that in general only those two need to be defined.
Applicative Helper
TypeClasses.mapn — Functionmapn(f, a1::F{T1}, a2::F{T2}, a3::F{T3}, ...) -> F{T}Apply a function over applicative contexts instead of plain values. Similar to Base.map, however sometimes the semantic differs slightly. TypeClasses.mapn always follows the semantics of TypeClasses.flatmap if defined.
E.g. for Base.Vector the Base.map function zips the inputs and checks for same length. On the other hand TypeClasses.mapn combines all combinations of inputs instead of the zip (which conforms with the semantics of flattening nested Vectors).
TypeClasses.@mapn — Macro@mapn f(a, b, c, d)translates to
mapn(f, a, b, c, d)TypeClasses.tupled — Functiontupled(Option(1), Option(2), Option(3)) == Option((1,2,3))
tupled(Option(1), None, Option(3)) == NoneCombine several Applicative contexts by building up a Tuple
Monad Core
TypeClasses.flatmap — Functionflatmap(function_returning_A, a::A)::Aflatmap applies a function to a container and immediately flattens it out. While map would give you A{A{...}}, flatmap gives you a plain A{...}, without any nesting.
If you define your own versions of flatmap, the recommendation is to apply a Base.convert after applying f. This makes sure your flatmap is typesafe, and actually enables sound interactions with other types which may be convertable to your A.
E.g. for Vector the implementation looks as follows:
TypeClasses.flatmap(f, v::Vector) = vcat((convert(Vector, f(x)) for x in v)...)Monad Helper
TypeClasses.flatten — Functionflatten(::A{A})::A = flatmap(identity, a)flatten gets rid of one level of nesting. Has a default fallback to use flatmap.
TypeClasses.:↠ — Functiona ↠ b = flatmap(_ -> b, a) # \twoheadrightarrowA convenience operator for monads which just applies the second monad within the first one.
The operator ↠ (\twoheadrightarrow) is choosen because the other favourite ≫ (\gg) which would have been in accordance with haskell unicode syntax for monads is unfortunately parsed as boolean comparison with extra semantics which leads to errors with non-boolean applications.
↠ is just the most similar looking other operator which does not have this restriction and is right-associative.
TypeClasses.@syntax_flatmap — Macro@syntax_flatmap begin
# Vector behave similar to nested for loops within @syntax_flatmap
a = [1, 2, 3]
b = [10, 20]
@pure a + b
end
# [11, 21, 12, 22, 13, 23]This is the standard monadic syntax which uses map for maplike and flatmap for flatmaplike. See Monadic.@monadic for more details.
Semigroup, Monoid, Alternative
Semigroup
TypeClasses.combine — Functioncombine(::T, ::T)::T # overload this
⊕(::T, ::T)::T # alias \oplus
combine(a, b, c, d, ...) # using combine(a, b) internallyAssocicative combinator operator.
The symbol ⊕ (\oplus) following http://hackage.haskell.org/package/base-unicode-symbols-0.2.3/docs/Data-Monoid-Unicode.html
Following Laws should hold
Associativity
⊕(::T, ⊕(::T, ::T)) == ⊕(⊕(::T, ::T), ::T)TypeClasses.:⊕ — Functioncombine(::T, ::T)::T # overload this
⊕(::T, ::T)::T # alias \oplus
combine(a, b, c, d, ...) # using combine(a, b) internallyAssocicative combinator operator.
The symbol ⊕ (\oplus) following http://hackage.haskell.org/package/base-unicode-symbols-0.2.3/docs/Data-Monoid-Unicode.html
Following Laws should hold
Associativity
⊕(::T, ⊕(::T, ::T)) == ⊕(⊕(::T, ::T), ::T)julia> using TypeClasses, Dictionaries
julia> dict1 = Dictionary([:a, :b, :c], ["1", "2", "3"])
3-element Dictionaries.Dictionary{Symbol, String}
:a │ "1"
:b │ "2"
:c │ "3"
julia> dict2 = Dictionary([:b, :c, :d], ["4", "5", "6"])
3-element Dictionaries.Dictionary{Symbol, String}
:b │ "4"
:c │ "5"
:d │ "6"
julia> dict1 ⊕ dict2
4-element Dictionaries.Dictionary{Symbol, String}
:a │ "1"
:b │ "24"
:c │ "35"
:d │ "6"Neutral
TypeClasses.neutral — Functionneutral
neutral(::Type)
neutral(_default_return_value) = neutralNeutral element for ⊕, also called "identity element". neutral is a function which can give you the neutral element for a concrete type, or alternatively you can use it as a singleton value which combines with everything.
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.
We decided for name neutral according to https://en.wikipedia.org/wiki/Identity_element. Alternatives seem inappropriate
- "identity" is already taken
- "identity_element" seems to long
- "I" is too ambiguous
- "unit" seems ambiguous with physical units
Following Laws should hold
Left Identity
⊕(neutral(T), t::T) == tRight Identity
⊕(t::T, neutral(T)) == tMonoid Helpers
TypeClasses.reduce_monoid — Functionreduce_monoid(itr; init=TypeClasses.neutral)Combines all elements of itr using the initial element init if given and TypeClasses.combine.
TypeClasses.foldr_monoid — Functionfoldr_monoid(itr; init=TypeClasses.neutral)Combines all elements of itr using the initial element init if given and TypeClasses.combine.
TypeClasses.foldl_monoid — Functionfoldl_monoid(itr; init=TypeClasses.neutral)Combines all elements of itr using the initial element init if given and TypeClasses.combine.
Alternative
TypeClasses.orelse — Functionorelse(a, b) # overload this
⊘(a, b) # alias \oslash
orelse(a, b, c, d, ...) # using orelse(a, b) internallyImplements an alternative logic, like having two options a and b, taking the first valid one. We decided for "orelse" instead of "alternatives" to highlight the intrinsic asymmetry in choosing.
The operator ⊘ (\oslash) is choosen to have an infix operator which is similar to \oplus, however clearly distinguishable, asymmetric, and somehow capturing a choice semantics. The slash actually is used to indicate choice (at least in some languages, like German), and luckily \oslash exists (and is not called \odiv).
TypeClasses.:⊘ — Functionorelse(a, b) # overload this
⊘(a, b) # alias \oslash
orelse(a, b, c, d, ...) # using orelse(a, b) internallyImplements an alternative logic, like having two options a and b, taking the first valid one. We decided for "orelse" instead of "alternatives" to highlight the intrinsic asymmetry in choosing.
The operator ⊘ (\oslash) is choosen to have an infix operator which is similar to \oplus, however clearly distinguishable, asymmetric, and somehow capturing a choice semantics. The slash actually is used to indicate choice (at least in some languages, like German), and luckily \oslash exists (and is not called \odiv).
orelse(d1::Dict, d2::Dict) -> DictFollowing the orelse semantics on Option values, the first value is retained, and the second is dropped. Hence this is the flipped version of Base.merge.
orelse(future1, future2, ...)
future1 ⊘ future2Runs both in parallel and collects which ever result is first. Then interrupts all other futures and returns the found result.
orelse(task1, task2, ...)
task1 ⊘ task2Runs both in parallel and collects which ever result is first. Then interrupts all other tasks and returns the found result.
orelse(d1::Dict, d2::Dict) -> DictFollowing the orelse semantics on Option values, the first value is retained, and the second is dropped. Hence this is the flipped version of Base.merge.
FlipTypes
TypeClasses.flip_types — Functionflip_types(value::T{S{A}})::S{T{A}}reverses the two outer containers, e.g. making an Array of Options into an Option of an Array.
TypeClasses.default_flip_types_having_pure_combine_apEltype — Functiondefault_flip_types_having_pure_combine_apEltype(container)Use this helper function to ease the definition of flip_types for your own type.
Note that the following interfaces are assumed:
- iterable
- pure
- combine
- ap on eltype
And in case of empty iterable in addition the following:
- neutral
- pure on eltype
We do not overload flip_types directly because this would require dispatching on whether isAp(eltype(T)). But relying on eltype to define different semantics is strongly discouraged.
TypeClasses.DataTypes
Iterable
TypeClasses.DataTypes.Iterables.Iterable — Typewrapper to clearly indicate that something should be treated as an Iterable
Callable
TypeClasses.DataTypes.Callable — Typewrapper to clearly indicate that something should be treated as a Callable
Writer
TypeClasses.DataTypes.Writers.Writer — Typelike Pair, however assumes that combine is defined for the accumulator acc
Note that neutral may indeed be undefined
TypeClasses.DataTypes.Writers.getaccumulator — Functiongetaccumulator(writer::Writer)Returns the accumulator of the Writer value.
Examples
julia> using TypeClasses
julia> getaccumulator(Writer("example-accumulator"))
"example-accumulator"State
TypeClasses.DataTypes.States.State — TypeState(func)
State() do state
....
return value, newstate
endState monad, which capsulate a state within a monadic type for monadic encapsulation of the state-handling.
You can run a state by either calling it, or by using Base.run. If no initial state is given, nothing is used.
TypeClasses.DataTypes.States.getstate — ConstantgetstateStandard value for returning the hidden state of the State Monad.
Examples
julia> using TypeClasses
julia> mystate = @syntax_flatmap begin
state = getstate
@pure println("state = $state")
end;
julia> mystate(42)
state = 42
(nothing, 42)TypeClasses.DataTypes.States.putstate — Functionputstate(x)putstate is a standard constructor for State objects which changes the underlying state to the given value.
Examples
julia> using TypeClasses
julia> mystate = @syntax_flatmap begin
putstate(10)
state = getstate
@pure println("The state is $state, and should be 10")
end;
julia> mystate()
The state is 10, and should be 10
(nothing, 10)