Closed dlfivefifty closed 5 years ago
Why not start with
abstract type Space{T} end
abstract type VectorSpace{T} <: Space{T} end
abstract type NormedVectorSpace{T} <: VectorSpace{T} end
abstract type HilbertSpace{T} <: NormedVectorSpace{T} end
Then, the vector spaces are all linear, and a space may provide evaluation, a norm, and an inner product and bilinear form.
The catch is what to do about finite-dimensional vector spaces (e.g. AbstractArray
s). Traits would include them wrapper-free.
Also, how would this framework deal with isometries (between \ell2(\N) and 2-normalized Legendre coefficients, say)?
Good point: these are traits, not inheritance: A space can either be a normed space (NormStyle
), a function space (EvaluationStyle
) or both.
I think this is the key: adjectives are traits, nouns are types. So Cat() isa Mammal
while AppearanceStyle(Cat()) == UglyStyle()
That's true: the inheritance I described is just one possibility (that works for complete orthonormal bases).
I was thinking LinearSpaces.jl might be another name, though it’s probably too generic.
PS here’s another one https://github.com/anj1/AffineSpaces.jl/blob/master/README.md
Also, how would this framework deal with isometries (between \ell2(\N) and 2-normalized Legendre coefficients, say)?
I don't think that makes sense: they are just different spaces.
What is the T
in Space{T}
? Functions involve two types, since each element of a function space presumably maps something of type S
to something of type T
. I'd go for FunctionSpace{S,T}
. One could have S = SVector{3,Float64}
and T=Float64
, for example.
Also, ideally, I'd like to distinguish between a space and the span of some basis for that space. By analogy to DomainSets.jl, where domains are equal if they describe the same set, functions are equal if they have the same outcome for the same input. Hence, the function x::S -> zero(T)
is the zero element of FunctionSpace{S,T}
, always. Bases, along with finite or infinite vectors of coefficients, are one step further down the road. A space could be defined by the span of some basis, for example.
Disclaimer, that is exactly what I did here in BasisFunctions.jl. I haven't really used these spaces though.
I guess it depends on what the aim of the package is, of course. In a package called FunctionSpaces.jl, I would expect to see L^2(Omega) where Omega is some domain.
Here's a crazy thought: a basis is an operator from (possibly infinite) vectors (EuclideanSpace
?) to a space:
struct ChebyshevBasis{T,D} <: Operator{T}
d::D
end
domainspace(::ChebyshevBasis{T}) where T = EuclideanSpace{T}(∞)
rangespace(b::ChebyshevBasis) = Chebyshev(b.d)
Then functions are lazy multiplication of this operator:
f = Mul(ChebyshevBasis(), Vcat([1,2,3], Zeros(∞)))
I would expect to see L^2(Omega) where Omega is some domain
Chebyshev()
could be code for 1/sqrt(1-x^2)
weighted L^2
on [-1,1]
. Though in terms of the basis, it all depends on the decay: if the coefficients are finite vectors, then the space is really PolynomialSpace()
, if the coefficient tail is (1:∞).^(-1)
which are in l^2, then the space is L^2
...
Bases as operators are not so crazy: it is the synthesis operator! I have an example too in BasisFunctions here. Again, experimental code, rarely ever used so far. It was meant to bring the code closer to a formal mathematical description of sampling.
For completeness, in my experience, the spaces I mentioned don't do much. It is somewhat nice to have a notion of L^2
. For example, if FunctionSpace{S,T}
has complex types, then this conveys some information: you can preallocate complex floats here and there. But the real work is of course indeed in the bases, the associated vector spaces, and the operators that act on them. That might as well be the goal here, else the package will be fairly empty.
This is also a good time to rethink norm(f::Fun{Chebyshev})
as implementing the L2([-1,1], dx) norm with Lebesgue measure. Arguably, orthogonal polynomial spaces should implement the norm
with the orthogonality measure.
With finite-dimensional coefficients, this doesn't matter. But with InfiniteArrays.jl, there is the possibility of summing sequences of coefficients whose weighted squares have a convergence trait, say the p-test.
That ones harder than it sounds. if I were to type
f = Fun(x -> exp(x), 0..1)
I'd expect norm(f)
to return Lebesgue over 0..1
. If I were to type
g = Fun(x -> exp(x), Chebyshev(0..1))
then maybe it would be fine for norm(g)
to give the weighted norm. But what about norm(f+g)
???
Maybe that's your expectation, but I'd expect the norm
to depend on the basis. It's a design choice that the default constructor is Chebyshev
.
With the proper help text, norm
can be consistent, and the user can do norm(Fun(f, Legendre(0..1)))
or even norm(Fun(f, Fourier(PeriodicInterval(0..1))))
!
We could always change the default segment constructor to Legendre :trollface:.
This is also complicated by considering other p-norms. Should the norm(::Fun{Chebyshev}, Inf)
be a weighted infinity-norm?
That’s what I suggested in my draft post...
The issue is that Space
and Basis
are really two different things. For finite coefficients the space is really PolynomialSpace
regardless of the basis. So using the basis to determine the norm is wrong.
I don’t know the right way to set it up, but to infer a space requires both knowledge of the basis and the (tail of the) coefficients.
To me for norm the following makes sense:
norm(f, Chebyshev()) # weighted norm, independent of the basis for f
norm(f) # either errors, or is Lebesgue on domain(f).
PS some history: ApproxFun.jl was originally named Funs.jl, until when trying to register Stefan suggested calling it ChebyshevFunctions.jl or ChebFun.jl 🤢. I was on a flight and quickly added support for Fourier to avoid those suggested names. I don’t remember why I used space
instead of basis
, probably was just because it sounded better.
The idea of distinguishing space from basis is growing on me. (Being used to the ApproxFun ecosystem, I was accustomed to the idea of a "Chebyshev" approximation space, but even that's not clear: with one summability condition it could represent continuous functions, and another condition leads to weighted square integrable. None of this mattered for finite-dimensional coefficients.) I suppose this repository would be for things like Lebesgue, Sobolev/BesselPotential, and Continuous{n} as examples of normed vector spaces along with bases (or perhaps bases should be in Bases.jl or Daan's BasisFunctions.jl)
Cheers,
Mikael
On Oct 20, 2018, at 1:54 PM, Sheehan Olver notifications@github.com<mailto:notifications@github.com> wrote:
That’s what I suggested in my draft post...
The issue is that Space and Basis are really two different things. For finite coefficients the space is really PolynomialSpace regardless of the basis. So using the basis to determine the norm is wrong.
I don’t know the right way to set it up, but to infer a space requires both knowledge of the basis and the (tail of the) coefficients.
To me for norm the following makes sense:
norm(f, Chebyshev()) # weighted norm, independent of the basis for f norm(f) # either errors, or is Lebesgue on domain(f).
PS some history: ApproxFun.jl was originally named Funs.jl, until when trying to register Stefan suggested calling it ChebyshevFunctions.jl or ChebFun.jl 🤢. I was on a flight and quickly added support for Fourier to avoid those suggested names. I don’t remember why I used space instead of basis, probably was just because it sounded better.
— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/JuliaApproximation/FunctionSpaces.jl/issues/1#issuecomment-431608811, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AHzBpZmRm1_DrrGyITGF2AwtnWonl0gkks5um3F9gaJpZM4Xx3yp.
Here are two options:
A Fun
consists of coefficients
in a basis
(two fields). Functions like norm
and bilinearform
dispatch based on typeof(coefficients)
, typeof(basis)
and a user-supplied Space
, e.g. L2 with the usual measure.
A Fun
in an approximation space
consists of coefficients
in a basis
(three fields). Functions like norm
and bilinearform
dispatch completely based on typeof(f)
.
Other questions linger:
Should constructors dispatch on the approximation space? Theoretically, the constructor seeks an approximation to satisfy ||f - f̃|| < tol||f|| or perhaps ||f - f̃|| < tol||f̃|| in practice. This requires a norm which requires a space.
Would all this abstraction lead to controversial defaults?
Are the current defaults (without the full abstraction) controversial?
Another big question: What is a Fun
?
Why is the current Fun
all the way down the hierarchy of islinear-hasnorm-hasbasis?
A Fun
could conceivably hold more generic fields to describe the approximation data, like rational or exponential models. In this context, RatFun
for example is really just an alias.
There's nothing forcing the expansion to be a basis, really it's just a quasi-matrix multiplication.
A Fun
is an expansion in a linear space. Getting rid of this results in a type with very little commonality.
Having slept on it I think the first version should just be a cleaned up version of what's already in ApproxFun. We can refactor later, but there are too many possibilities, and very little improvement results from these changes.
One change I might go through with: instead of storing coefficients as a vector [1,2,3]
, they would be stored in infinite-dimensional form:
Vcat([1,2,3], Zeros(∞))
This has no increased storage and can still be used as before via: instead of cfs = coefficients(f)
one would use
cfs,_ = coefficients(f).arrays
But it gets rid of pretty much all k ≤ ncoefficients(f) ? ... : ...
.
Infinite arrays are a big assumption to make on the space, though it indeed looks like a neat way to express the algorithms of ApproxFun
. It makes sense in a setting where there is a strictly nested sequence of subspaces, where in addition there is an infinite basis that can be truncated to yield a basis for any subspace. This is true in the setting of spectral methods.
More generally, for function approximation problems, there is a sequence of finite-dimensional subspaces that are ultimately dense in an infinite-dimensional space. Each of those subspaces has its own basis, and there are linear operators to convert (approximately) between the corresponding expansions. Think of finite elements, for example, where the conversions could be prolongation and restriction, or refinement and coarsening. Ideally, we can support this more general setting too.
From our discussion, it currently seems like a Fun
has three properties: a set of coefficients, a linear space which it is an element of, and a "basis" (which we now think of as a map from the coefficients to the actual element of that space, and it needs a different name). The Fun
doesn't necessarily store these three explicitly, but you can ask about them. Makes sense? Sounds much like @MikaelSlevinsky's option 2 above.
If a Fun
does become part of this package, what might be neat and useful is a way to simply make linear combinations of Fun
's with some sanity checks: you can add Fun
's only if they are in the same space, for example, or if they can easily be converted. Anybody out there who has ever overloaded +
and *
to essentially add vectors hidden inside some expansion type could benefit from the framework.
what might be neat and useful is a way to simply make linear combinations of Fun's with some sanity checks: you can add Fun's only if they are in the same space
You mean exactly like what's in ApproxFun already?
Infinite arrays are a big assumption to make on the space
Sorry, I meant
pad(cfs, dimension(space))
. This would return Vcat(cfs, Zeros(∞))
if dimension(space) == ∞
, otherwise it would just pad to the dimension.
Note ApproxFun already supports finite dimensional spaces, and includes splines and a few others. What would be needed for FEM would be a family of spaces, this fits better as something on top of spaces.
what might be neat and useful is a way to simply make linear combinations of Fun's with some sanity checks: you can add Fun's only if they are in the same space
You mean exactly like what's in ApproxFun already?
For the most part, yes, like spacescompatible
. If you can separate the descriptive part of spaces and expansions from the approximation algorithms in ApproxFun, I think it could be useful for others. I'd use it.
I still think of L^2
when I hear FunctionSpaces.jl
, and perhaps a way to declare that one space is embedded into another one. Throw in an abstract Fun
object and I hear ApproximationSpaces.jl
.
To continue refining the ideas above, one option is to rename the package VectorSpaces.jl
and define:
struct Vec{VSet, VSpace, T, VType}
set::VSet
space::VSpace
coefficients::VType
end
set
describes the set of vectors in the quasi-matrixspace
describes the vector spacecoefficients
...There would be a reasonable alias for a Fun
based on FunctionSet <: VectorSet
and FunctionSpace <: VectorSpace
.
Two more traits to consider are iscomplete
and isspannedby
. For example,
iscomplete(::Lebesgue{1}) = true # Lebesgue{1} is printed as a BanachSpace rather than an NormedVectorSpace
and
isspannedby(L::Lebesgue{2,D<:Domain}, set::Legendre{D<:Domain) where D = true
With trait-like design it's no longer the case that everything needs to be a templated out: the space
can be inferred from set
and coefficients
.
I would design it like this, where a function set is represented by a quasi-matrix, that is, an operator, and expansions in a basis are just lazy multiplication. Here I show how it would work even for infinite vectors of coefficients:
abstract type AbstractArrayFunction{T,N} end # represents array-valued functions
AbstractVectorFunction{T} = AbstractArrayFunction{T,1}
struct Legendre{T} <: VectorFunction{T} end # Legendre()(0.3) is equal to the infinite-vector `[P_0(x), P_1(x), …]
length(::Legendre) = ∞
const Fun{BASIS, T, VT} = Mul{T, BasisStyle, <:Any, Adjoint{T,BASIS <: AbstractVectorFunction}, VT<:AbstractVector}
struct AlgebraicGrowth{BASIS} <: FunctionSpace
vset::BASIS
rate::Float64
end
struct UnknownGrowth{BASIS} <: FunctionSpace
vset::BASIS
end
const L2 = Sobolev{2,0}
issubset(sp::AlgebraicGrowth{<:Legendre}, ::L2) = sp.rate < -0.5
issubset(sp::AlgebraicGrowth{<:Legendre}, ::Sobolev{2,R}) where R = sp.rate < -0.5-R
coefficients(f::Fun) = f.B
space(f::Fun) = _space(f.A', f.B)
_space(sp::Legendre, cfs::Vcat) = _space(sp, last(cfs.arrays)) # the tail determines everything
_space(sp::Legendre, ::Zeros) = PolynomialSpace(ChebyshevInterval())
_space(sp::Legendre, ::AbstractFill) = AlgebraicGrowth(sp, 0)
_space(sp::Legendre, ::InfRange) = AlgebraicGrowth(sp, 1)
function _space(sp::Legendre, cfs::Broadcasted{<:Any,<:Any,typeof(^),Tuple{<:InfRange,<:Integer})
_, p = cfs.args
AlgebraicGrowth(sp, p)
end
_space(sp::Legendre, _) = UnknownGrowth(sp)
## Tests
x = 0.3
P = Legendre()
@test isinf(P)
@test isinf(P(x)) # lazily evaluated
@test P(x)[1:3] == legendrep.(1:3, x)
f = Mul(P', Vcat([1,2,3], Zeros(∞))) # represent P_0(x) + 2P_1(x) + 3P_2(x)
@test f(x) == P(x)[1:3]'*coefficients(f)[1:3]
@test space(f) == PolynomialSpace(ChebyshevInterval())
g = Mul(P',(1:∞).^(-1))
@test space(g) ⊆ L2(ChebyshevInterval())
@test norm(g) == sqrt(π^2/6)
@test_throws InexactError g(0.1) # Evaluation undefined
h = Mul(P',(1:∞).^(-2))
@test space(h) ⊆ L2(ChebyshevInterval())
@test space(h) ⊆ H1(ChebyshevInterval())
@test h(0.1) == ? # requires infinite-dimensional Clenshaw with the prescribed tail (Too Easy.)
There are other uses for a function space other than looking at coefficient tails. Ex: the constructor (even if the result is finite-dimensional). In this sense, the space could give you historical information about the Fun
. I don't know if this merits a field in the type, but something to think about.
Another way to store a Fun
(with some acknowledgeable disadvantages) is through its domain of definition, and equations it satisfies. For example:
f = Fun(exp, -1..1)
f.domain == -1..1
f.equations == [Evaluation(Point(1.0)) - exp(1.0); Derivative(-1..1) - I]
There are other uses for a function space … Ex: the constructor
This is really a property of the basis, and the finite constructor could be written as:
x = ChebyshevGrid(-1..1) # lazy chebyshev points
V = P[1:n].(x) # Vandermonde matrix, where the rules of broadcast for `VectorFunction`s are defined to return an operator. So this would be an operator from expansions in P[1:n] to functions evaluated on the Chebyshev grid
f = V \ exp.(x) # returns Mul(P[1:n]', AbstractMatrix(V) \ Vector(exp.(x)))
Another way to store a Fun (with some acknowledgeable disadvantages) is through its domain of definition, and equations it satisfies
Indeed, which can be done with lazy Ldiv
:
f = Ldiv([Evaluation(Point(1.0)); Derivative(-1..1) - I], [exp(1.0); 0])
If the function space is dictated by traits acting on the coefficients (software-wise), then how will this framework be wide enough to include the FrameFun
package? In many of the same function spaces philosophically shared with ApproxFun
, the generated expansions in certain function sets in FrameFun
may not have any decay at all.
Even in ApproxFun
, this limits the use of function sets, e.g. two orthonormal bases spanning the same Hilbert space, to direct-sum construction (via ⊕
or ⊖
).
In FrameFun they are finite dimensional, so the space would be Span(basis)
Actually functions don’t have spaces, anymore than the number 0.3 has a domain. Functions are in spaces, and can be in multiple spaces.
Good point.
Right, and verifying membership numerically will be difficult in general, even if coefficients do decay - which in the case of frames they might not even do.
At one point in our code we tried to make everything revolve around spaces rather than bases (we only used the span of a truncated basis as a space), but that was a disaster. It led to repeated wrapping and unwrapping of the basis: the basis really is the workhorse that captures the semantics of the coefficients.
I think functional analysis is not the right language for concrete realisations on a computer. Spaces and operators in the mathematical sense are not actually useful.
I think the right notions are quasi-matrices (to represent bases), which multiply vectors and return functions. Essentially analogous to the old RowVector
.
We may also need adjoint of functions (bra-ket) to express analysis and synthesis, and thereby conversion, but I’ll have to think some more.
Right, and verifying membership numerically will be difficult in general, even if coefficients do decay
Not true: using interval arithmetic and automatic differentiation (or evaluation in the complex plane) one can rigorously and automatically determine decay rates of the tail.
g = Mul(P',(1:∞).^(-1)) @test space(g) ⊆ L2(ChebyshevInterval()) @test norm(g) == sqrt(π^2/6)
Imagine dispatching to
zeta
, hehe
Let's say we thought in terms of Bra-Ket notation, so that functions are kets and functionals are bras. Then we have that quasi-matrices are row-vectors of kets. Their adjoint is a column-vector of bras.
Then the following could represent conversion:
abstract type QuasiVector{T} end # Vector of bras
const QuasiMatrix{T} = Transpose{T, <:QuasiVector{T}} # Quasi-matrices are represented by transpose
# adjoint(::QuasiMatrix) gives a row-vector of bras
struct Jacobi{T} <: QuasiVector{T}
a::T
b::T
end
struct Legendre{T} <: QuasiVector{T} end
S = transpose(Legendre()) # The synthesis operator, row vector of kets
A = adjoint.(Legendre()) # The analysis operator, Column vector bras
@test A*S == Eye(∞)
@test S*A == IdentityOperator()
f = S * Vcat([1,2,3],Zeros(∞)) # in practice a lazy Mul
@test A * f == Vcat([1,2,3],Zeros(∞))
M = adjoint.(Jacobi(1,1)) * S # Conversion matrix from Legendre to Jacobi coefficients, whose entries are inner products. In practice, represented lazily with entries determined by exact formula
@test M isa AbstractMatrix
@test size(M) == (∞,∞)
C = transpose(Jacobi(1,1)) * M * A # Conversion operator
@test (C*f)(0.1) == f(0.1)
EDIT: Actually, just make bases quasi matrices:
abstract type QuasiMatrix{T} end # row vector of bras
# adjoint(::QuasiMatrix) gives column vector of kets
struct Jacobi{T} <: QuasiMatrix{T}
a::T
b::T
end
struct Legendre{T} <: QuasiVector{T} end
S = Legendre() # The synthesis operator, row vector of kets
A = adjoint(Legendre()) # The analysis operator, Column vector bras
@test A*S == Eye(∞)
@test S*A == IdentityOperator()
f = S * Vcat([1,2,3],Zeros(∞)) # in practice a lazy Mul
@test A * f == Vcat([1,2,3],Zeros(∞))
M = adjoint(Jacobi(1,1)) * S # Conversion matrix from Legendre to Jacobi coefficients, whose entries are inner products. In practice, represented lazily with entries determined by exact formula
@test M isa AbstractMatrix
@test size(M) == (∞,∞)
C =Jacobi(1,1) * M * A # Conversion operator
@test (C*f)(0.1) == f(0.1)
I think functional analysis is not the right language for concrete realisations on a computer. Spaces and operators in the mathematical sense are not actually useful.
What did you originally have in mind for this FunctionSpaces.jl
package? A way to formally describe spaces, or a generic way to represent function approximations like we are discussing now? (Either way, whatever the outcome here, by January I'll start integrating more with ApproxFun or with this package.)
I was going to say "let's not invent new terminology" but a QuasiMatrix
(or QuasiArray
?) is starting to make sense. One (or more?) of the dimensions is simply continuous. There is no assumption in the terminology whatsoever on the continuous dimension being a basis, a frame or a "dictionary". A function can be a linear combination over the discrete dimensions of the QuasiArray
.
The analogy with row and column vectors is lost if both the function set and the coefficients have tensor product structure. An expansion is like a dot-product of the quasi-matrix and the coefficient matrix, and then a sum over them. What kind of operation is that? And is it fair to say that a QuasiMatrix has as many continuous dimensions as it has discrete ones? That is the case at least with a tensor product basis: indices i and j and variables x and y, say.
Quasi-matrix is an established Trefethenism, not a new term
I like the idea that a basis is simply a QuasiMatrix
or QuasiArray
. One thing that bugs me is that a function space does not depend on the ordering of the basis, but a QuasiVector
has a built in ordering. Is the function space of a polynomial basis whose indices go 0,1,2,.. as usual the same as that with the first two basis elements switched? More pertinently, the coefficients of a bivariate function can be indexed in an array in several ways, or made into a one-dimensional vector in many ways. Are all of these function spaces different?
It would have to be the case that the iteration over a quasi matrix happens in the same order as the iteration over any set of coefficients.
The span of two permuted bases is the same and we should treat it as such. However, the expansion in a particular basis is specific to the ordering of that basis. The fact that the spaces are the same only means that the expansion in one basis can be converted to the other basis. In this case, the conversion would be a permutation? Is that satisfactory?
Quasi-matrix is an established Trefethenism, not a new yerm
For reference quoting the link here to quasi-matrices in Chebfun. Nick Trefethen also cites Stewart for the name. The name didn't really catch on so far, but the concept really seems to makes sense as an implementation strategy in Julia.
An AbstractArray{T,N}
has two type parameters: the dimension and the element type. This implies in the case where N=2
that a[i,j]
has type T
. In particular, the array does not prescribe the type of the indices.
Does this mean that a possible full signature is QuasiArray{T,M,N}
? It means in case M=3
and N=2
that a[i,j](x,y,z)
has type T
. In other words, T
is the type of the codomain of the functions, and the type of the domain is not part of the signature. And the type of a[:,2](:,2.0,:)
is QuasiArray{T,1,2}
?
Suppose Vec
is a type that denotes a linear vector space: this could be functions or anything else. Suppose their shape in Julia is scalar (like functions). Then a quasi-matrix is just an Adjoint{Vec, <:AbstractVector{Vec}}
and LinearAlgebra.jl takes care of the rest.
Now Function
is not a linear space, and in this setting we want multiplication to be lazy as "expansion in a basis" just means lazy multiplication of a quasi-matrix times a vector. And there's a bit of a chicken-and-egg situation: we want to represent functions as expansions in a basis, but then the elements of the basis is itself a function. So there's some more thought to be had.
In terms of span, do we need this? I think for matrices one would do a qr
and check that the Q
s are the same.
As a sidenote, I'm not convinced we should aim for general vector spaces. AbstractArray
itself already does this very well, I don't see what we have to offer here over Point{T} <: AbstractVector{T}
. Or am I missing something?
If the multiplication of a QuasiVector
with a vector of coefficients results in a Fun
, that is precisely lazy multiplication: it is just grouping two objects together. Any concrete subtype of QuasiVector
should know how to evaluate an entry i
in a given point x
. If you have a QuasiVector
that represents a basis, that amounts to evaluating the basis function. One could make a concrete QuasiVector
that contains a bunch of Fun
's (which would correspond more closely to a quasimatrix as in Chebfun I think), then evaluation is implemented in terms of evaluating the corresponding Fun
?
@dlfivefifty Sorry, I misread your post. Your type Vec
is a scalar element that possibly represents a function -- okay. You need something like that if you inherit from AbstractArray
in order to say what its element type is. But we can't do that, unless Vec
is something like Function
(which would be abstract and slow) or if Vec
itself encodes the basis. I think we don't have that problem if QuasiVector
does not inherit from AbstractArray
, but then the methods of LinearAlgebra.jl won't apply. Perhaps a QuasiVector
can be a matrix, where the second dimension is continuous, and then we only have to tell AbstractArray
what the type is of any function evaluation.
Perhaps some experimenting is called for.
Isn’t a FunctionSpace (almost by definition) just a Space with evaluation defined (that respects the linearity of the space)?
It almost feels like
Fun
is misnomer, as the space could be something other than a FunctionSpace. Perhaps its more general to do the following: