Skip to content

Commit ceb4dd3

Browse files
committed
Initial Implementation and Style
1 parent 0a06599 commit ceb4dd3

5 files changed

Lines changed: 205 additions & 4 deletions

File tree

examples/README.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# # FunctionImplementations.jl
2-
#
2+
#
33
# [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://itensor.github.io/FunctionImplementations.jl/stable/)
44
# [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://itensor.github.io/FunctionImplementations.jl/dev/)
55
# [![Build Status](https://github.com/ITensor/FunctionImplementations.jl/actions/workflows/Tests.yml/badge.svg?branch=main)](https://github.com/ITensor/FunctionImplementations.jl/actions/workflows/Tests.yml?query=branch%3Amain)

src/FunctionImplementations.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module FunctionImplementations
22

3-
# Write your package code here.
3+
include("implementation.jl")
4+
include("style.jl")
45

56
end

src/implementation.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
struct Implementation{F, Style} <: Function
2+
f::F
3+
style::Style
4+
end

src/style.jl

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
### This is based on the BroadcastStyle code in
2+
### https://github.com/JuliaLang/julia/blob/master/base/broadcast.jl
3+
### Objects with customized behavior for a certain function should declare a Style
4+
5+
"""
6+
`Style` is an abstract type and trait-function used to determine behavior of
7+
objects. `Style(typeof(x))` returns the style associated
8+
with `x`. To customize the behavior of a type, one can declare a style
9+
by defining a type/method pair
10+
11+
struct MyContainerStyle <: Style end
12+
FunctionImplementations.Style(::Type{<:MyContainer}) = MyContainerStyle()
13+
14+
"""
15+
abstract type Style end
16+
17+
struct UnknownStyle <: Style end
18+
Style(::Type{Union{}}, slurp...) = UnknownStyle() # ambiguity resolution
19+
20+
"""
21+
`FunctionImplementations.AbstractArrayStyle{N} <: Style` is the abstract supertype for any style
22+
associated with an `AbstractArray` type.
23+
The `N` parameter is the dimensionality, which can be handy for AbstractArray types
24+
that only support specific dimensionalities:
25+
26+
struct SparseMatrixStyle <: FunctionImplementations.AbstractArrayStyle{2} end
27+
FunctionImplementations.Style(::Type{<:SparseMatrixCSC}) = SparseMatrixStyle()
28+
29+
For `AbstractArray` types that support arbitrary dimensionality, `N` can be set to `Any`:
30+
31+
struct MyArrayStyle <: FunctionImplementations.AbstractArrayStyle{Any} end
32+
FunctionImplementations.Style(::Type{<:MyArray}) = MyArrayStyle()
33+
34+
In cases where you want to be able to mix multiple `AbstractArrayStyle`s and keep track
35+
of dimensionality, your style needs to support a [`Val`](@ref) constructor:
36+
37+
struct MyArrayStyleDim{N} <: FunctionImplementations.AbstractArrayStyle{N} end
38+
(::Type{<:MyArrayStyleDim})(::Val{N}) where N = MyArrayStyleDim{N}()
39+
40+
Note that if two or more `AbstractArrayStyle` subtypes conflict, the resulting
41+
style will fall back to that of `Array`s. If this is undesirable, you may need to
42+
define binary [`Style`](@ref) rules to control the output type.
43+
44+
See also [`FunctionImplementations.DefaultArrayStyle`](@ref).
45+
"""
46+
abstract type AbstractArrayStyle{N} <: Style end
47+
48+
"""
49+
`FunctionImplementations.ArrayStyle{MyArrayType}()` is a [`FunctionImplementations.Style`](@ref) indicating that an object
50+
behaves as an array. It presents a simple way to construct
51+
[`FunctionImplementations.AbstractArrayStyle`](@ref)s for specific `AbstractArray` container types.
52+
Styles created this way lose track of dimensionality; if keeping track is important
53+
for your type, you should create your own custom [`FunctionImplementations.AbstractArrayStyle`](@ref).
54+
"""
55+
struct ArrayStyle{A <: AbstractArray} <: AbstractArrayStyle{Any} end
56+
ArrayStyle{A}(::Val) where {A} = ArrayStyle{A}()
57+
58+
"""
59+
`FunctionImplementations.DefaultArrayStyle{N}()` is a [`FunctionImplementations.Style`](@ref) indicating that an object
60+
behaves as an `N`-dimensional array. Specifically, `DefaultArrayStyle` is
61+
used for any
62+
`AbstractArray` type that hasn't defined a specialized style, and in the absence of
63+
overrides from other arguments the resulting output type is `Array`.
64+
When there are multiple inputs, `DefaultArrayStyle` "loses" to any other [`FunctionImplementations.ArrayStyle`](@ref).
65+
"""
66+
struct DefaultArrayStyle{N} <: AbstractArrayStyle{N} end
67+
DefaultArrayStyle(::Val{N}) where {N} = DefaultArrayStyle{N}()
68+
DefaultArrayStyle{M}(::Val{N}) where {N, M} = DefaultArrayStyle{N}()
69+
const DefaultVectorStyle = DefaultArrayStyle{1}
70+
const DefaultMatrixStyle = DefaultArrayStyle{2}
71+
Style(::Type{<:AbstractArray{T, N}}) where {T, N} = DefaultArrayStyle{N}()
72+
Style(::Type{T}) where {T} = DefaultArrayStyle{ndims(T)}()
73+
74+
# `ArrayConflict` is an internal type signaling that two or more different `AbstractArrayStyle`
75+
# objects were supplied as arguments, and that no rule was defined for resolving the
76+
# conflict. The resulting output is `Array`. While this is the same output type
77+
# produced by `DefaultArrayStyle`, `ArrayConflict` "poisons" the Style so that
78+
# 3 or more arguments still return an `ArrayConflict`.
79+
struct ArrayConflict <: AbstractArrayStyle{Any} end
80+
ArrayConflict(::Val) = ArrayConflict()
81+
82+
### Binary Style rules
83+
"""
84+
Style(::Style1, ::Style2) = Style3()
85+
86+
Indicate how to resolve different `Style`s. For example,
87+
88+
Style(::Primary, ::Secondary) = Primary()
89+
90+
would indicate that style `Primary` has precedence over `Secondary`.
91+
You do not have to (and generally should not) define both argument orders.
92+
The result does not have to be one of the input arguments, it could be a third type.
93+
"""
94+
Style(::S, ::S) where {S <: Style} = S() # homogeneous types preserved
95+
# Fall back to UnknownStyle. This is necessary to implement argument-swapping
96+
Style(::Style, ::Style) = UnknownStyle()
97+
# UnknownStyle loses to everything
98+
Style(::UnknownStyle, ::UnknownStyle) = UnknownStyle()
99+
Style(::S, ::UnknownStyle) where {S <: Style} = S()
100+
# Precedence rules
101+
Style(::A, ::A) where {A <: ArrayStyle} = A()
102+
Style(::ArrayStyle, ::ArrayStyle) = UnknownStyle()
103+
Style(::A, ::A) where {A <: AbstractArrayStyle} = A()
104+
function Style(a::A, b::B) where {A <: AbstractArrayStyle{M}, B <: AbstractArrayStyle{N}} where {M, N}
105+
if Base.typename(A) === Base.typename(B)
106+
return A(Val(max(M, N)))
107+
end
108+
return UnknownStyle()
109+
end
110+
# Any specific array type beats DefaultArrayStyle
111+
Style(a::AbstractArrayStyle{Any}, ::DefaultArrayStyle) = a
112+
Style(a::AbstractArrayStyle{N}, ::DefaultArrayStyle{N}) where {N} = a
113+
Style(a::AbstractArrayStyle{M}, ::DefaultArrayStyle{N}) where {M, N} =
114+
typeof(a)(Val(max(M, N)))
115+
116+
## logic for deciding the Style
117+
118+
"""
119+
combine_styles(cs...)::Style
120+
121+
Decides which `Style` to use for any number of value arguments.
122+
Uses [`Style`](@ref) to get the style for each argument, and uses
123+
[`result_style`](@ref) to combine styles.
124+
125+
# Examples
126+
```jldoctest
127+
julia> FunctionImplementations.combine_styles([1], [1 2; 3 4])
128+
FunctionImplementations.DefaultArrayStyle{2}()
129+
```
130+
"""
131+
function combine_styles end
132+
133+
combine_styles() = DefaultArrayStyle{0}()
134+
combine_styles(c) = result_style(Style(typeof(c)))
135+
combine_styles(c1, c2) = result_style(combine_styles(c1), combine_styles(c2))
136+
@inline combine_styles(c1, c2, cs...) = result_style(combine_styles(c1), combine_styles(c2, cs...))
137+
138+
"""
139+
result_style(s1::Style[, s2::Style])::Style
140+
141+
Takes one or two `Style`s and combines them using [`Style`](@ref) to
142+
determine a common `Style`.
143+
144+
# Examples
145+
146+
```jldoctest
147+
julia> FunctionImplementations.result_style(FunctionImplementations.DefaultArrayStyle{0}(), FunctionImplementations.DefaultArrayStyle{3}())
148+
FunctionImplementations.DefaultArrayStyle{3}()
149+
150+
julia> FunctionImplementations.result_style(FunctionImplementations.UnknownStyle(), FunctionImplementations.DefaultArrayStyle{1}())
151+
FunctionImplementations.DefaultArrayStyle{1}()
152+
```
153+
"""
154+
function result_style end
155+
156+
result_style(s::Style) = s
157+
function result_style(s1::S, s2::S) where {S <: Style}
158+
return s1 s2 ? s1 : error("inconsistent styles, custom rule needed")
159+
end
160+
# Test both orders so users typically only have to declare one order
161+
result_style(s1, s2) = result_join(s1, s2, Style(s1, s2), Style(s2, s1))
162+
163+
# result_join is the final arbiter. Because `Style` for undeclared pairs results in UnknownStyle,
164+
# we defer to any case where the result of `Style` is known.
165+
result_join(::Any, ::Any, ::UnknownStyle, ::UnknownStyle) = UnknownStyle()
166+
result_join(::Any, ::Any, ::UnknownStyle, s::Style) = s
167+
result_join(::Any, ::Any, s::Style, ::UnknownStyle) = s
168+
# For AbstractArray types with undefined precedence rules,
169+
# we have to signal conflict. Because ArrayConflict is a subtype of AbstractArray,
170+
# this will "poison" any future operations (if we instead returned `DefaultArrayStyle`, then for
171+
# 3-array functions returned type would depend on argument order).
172+
result_join(::AbstractArrayStyle, ::AbstractArrayStyle, ::UnknownStyle, ::UnknownStyle) =
173+
ArrayConflict()
174+
# Fallbacks in case users define `rule` for both argument-orders (not recommended)
175+
result_join(::Any, ::Any, s1::S, s2::S) where {S <: Style} = result_style(s1, s2)
176+
177+
@noinline function result_join(::S, ::T, ::U, ::V) where {S, T, U, V}
178+
error(
179+
"""
180+
conflicting rules defined
181+
FunctionImplementations.Style(::$S, ::$T) = $U()
182+
FunctionImplementations.Style(::$T, ::$S) = $V()
183+
One of these should be undefined (and thus return FunctionImplementations.UnknownStyle)."""
184+
)
185+
end

test/test_basics.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
using FunctionImplementations: FunctionImplementations
1+
import FunctionImplementations as FI
22
using Test: @test, @testset
33

44
@testset "FunctionImplementations" begin
5-
# Tests go here.
5+
@testset "Implementation" begin
6+
struct MyAddAlgorithm end
7+
f = FI.Implementation(+, MyAddAlgorithm())
8+
@test f.f +
9+
@test f.style MyAddAlgorithm()
10+
(::typeof(f))(x, y) = "My add"
11+
@test f(2, 3) == "My add"
12+
@test f.f +
13+
@test f.style MyAddAlgorithm()
14+
end
15+
@testset "Style" begin
16+
end
617
end

0 commit comments

Comments
 (0)