TypeClasses Public API

Functor, Applicative, Monad

Foreach, using Base.foreach

TypeClasses.@syntax_foreachMacro
@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.

source

Functor, using Base.map

TypeClasses.@syntax_mapMacro
@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.

source

Applicative Core

TypeClasses.apFunction

ap(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.

source

Applicative Helper

TypeClasses.mapnFunction
mapn(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).

source
TypeClasses.tupledFunction
tupled(Option(1), Option(2), Option(3)) == Option((1,2,3))
tupled(Option(1), None, Option(3)) == None

Combine several Applicative contexts by building up a Tuple

source

Monad Core

TypeClasses.flatmapFunction
flatmap(function_returning_A, a::A)::A

flatmap 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)...)
source

Monad Helper

TypeClasses.flattenFunction
flatten(::A{A})::A = flatmap(identity, a)

flatten gets rid of one level of nesting. Has a default fallback to use flatmap.

source
TypeClasses.:↠Function
a ↠ b = flatmap(_ -> b, a) # \twoheadrightarrow

A 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.

source
TypeClasses.@syntax_flatmapMacro
@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.

source

Semigroup, Monoid, Alternative

Semigroup

TypeClasses.combineFunction
combine(::T, ::T)::T  # overload this
⊕(::T, ::T)::T  # alias \oplus
combine(a, b, c, d, ...)  # using combine(a, b) internally

Associcative 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)
source
TypeClasses.:⊕Function
combine(::T, ::T)::T  # overload this
⊕(::T, ::T)::T  # alias \oplus
combine(a, b, c, d, ...)  # using combine(a, b) internally

Associcative 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)
source
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"
source

Neutral

TypeClasses.neutralFunction
neutral
neutral(::Type)
neutral(_default_return_value) = neutral

Neutral 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) == t

Right Identity

  ⊕(t::T, neutral(T)) == t
source

Monoid Helpers

TypeClasses.reduce_monoidFunction
reduce_monoid(itr; init=TypeClasses.neutral)

Combines all elements of itr using the initial element init if given and TypeClasses.combine.

source
TypeClasses.foldr_monoidFunction
foldr_monoid(itr; init=TypeClasses.neutral)

Combines all elements of itr using the initial element init if given and TypeClasses.combine.

source
TypeClasses.foldl_monoidFunction
foldl_monoid(itr; init=TypeClasses.neutral)

Combines all elements of itr using the initial element init if given and TypeClasses.combine.

source

Alternative

TypeClasses.orelseFunction
orelse(a, b)  # overload this
⊘(a, b)  # alias \oslash
orelse(a, b, c, d, ...)  # using orelse(a, b) internally

Implements 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).

source
TypeClasses.:⊘Function
orelse(a, b)  # overload this
⊘(a, b)  # alias \oslash
orelse(a, b, c, d, ...)  # using orelse(a, b) internally

Implements 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).

source
orelse(d1::Dict, d2::Dict) -> Dict

Following 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.

source
orelse(future1, future2, ...)
future1 ⊘ future2

Runs both in parallel and collects which ever result is first. Then interrupts all other futures and returns the found result.

source
orelse(task1, task2, ...)
task1 ⊘ task2

Runs both in parallel and collects which ever result is first. Then interrupts all other tasks and returns the found result.

source
orelse(d1::Dict, d2::Dict) -> Dict

Following 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.

source

FlipTypes

TypeClasses.flip_typesFunction
flip_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.

source
TypeClasses.default_flip_types_having_pure_combine_apEltypeFunction
default_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.

source

TypeClasses.DataTypes

Iterable

Callable

Writer

State

TypeClasses.DataTypes.States.StateType
State(func)

State() do state
  ....
  return value, newstate
end

State 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.

source
TypeClasses.DataTypes.States.getstateConstant
getstate

Standard 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)
source
TypeClasses.DataTypes.States.putstateFunction
putstate(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)
source