I’ve got a syntax error in my code (in a function in a module) and I can’t seem to figure it out. The expression goes something like this:
ntuple(
let a = b
i -> expr(a, i)
end,
Val{c}())
According to the error message, it seems that Julia expects the entire let
expression should be a function argument name!?
julia> include("HeterogeneousArrays.jl")
Main.HeterogeneousArrays
julia> include("LayoutTrees.jl")
ERROR: LoadError: syntax: "let dim = dim, axs = axs, ta = ta
# /home/nsajko/src/gitlab.com/nsajko/BenchmarkVisualization/src/LayoutTrees.jl, line 189
i -> begin
# /home/nsajko/src/gitlab.com/nsajko/BenchmarkVisualization/src/LayoutTrees.jl, line 189
ifelse((i == dim), ta, axs[i])
end
end" is not a valid function argument name around /home/nsajko/src/gitlab.com/nsajko/BenchmarkVisualization/src/LayoutTrees.jl:177
Stacktrace:
[1] top-level scope
@ ~/src/gitlab.com/nsajko/BenchmarkVisualization/src/LayoutTrees.jl:164
Here’s my code:
HeterogeneousArrays.jl
# Copyright 2022 Neven Sajko. All right reserved.
module HeterogeneousArrays
using LinearAlgebra
export AbstractHeterogeneousArray, HSArray
# An array with the given axes. Heterogeneous in general, in the sense
# that different elements can have different types.
abstract type AbstractHeterogeneousArray{axes} end
Base.axes(::AbstractHeterogeneousArray{a}) where {a} = a
Base.axes(::AbstractHeterogeneousArray{a}, i) where {a} = a[i]
Base.size(::AbstractHeterogeneousArray{a}) where {a} = map(length, a)
Base.size(::AbstractHeterogeneousArray{a}, i) where {a} = length(a[i])
Base.length(::AbstractHeterogeneousArray{a}) where {a} = prod(length, a)
Base.getindex(ar::AbstractHeterogeneousArray{a},
i::CartesianIndex{n}) where {a, n} = ar[Tuple(i)...]
# Heterogeneous static array.
struct HSArray{
axes, size, len, dim_count,
ind_helper_mul,
ind_helper_diff,
T <: NTuple{len, Any}} <: AbstractHeterogeneousArray{axes}
data::T
function indexing_helper_mul(::Val{axes}) where {axes}
local t = ntuple(i -> length(axes[i + 1]), Val{max(0, length(axes) - 1)}())
ntuple(
let t = t
i -> prod(t[begin+i-1 : end])
end,
Val{length(axes)}())
end
function HSArray{a}(t::T) where {a, T <: Tuple}
isa(a, NTuple{n, AbstractUnitRange{Int}} where {n}) ||
error("axes not a tuple of Int unit ranges")
local size = map(length, a)
local len = prod(length, a)
local dc = length(a)
local ind_helper_mul = indexing_helper_mul(Val{a}())
local ind_helper_diff = ntuple(i -> first(a[i]), Val{dc}())
new{a, size, len, dc, ind_helper_mul, ind_helper_diff, T}(t)
end
end
check_bounds(r::NTuple{n, AbstractUnitRange{Int}},
i::Vararg{Int, n}) where {n} =
for p in zip(r, i)
(p[2] in p[1]) || Base.throw_boundserror(p[1], p[2])
end
function Base.getindex(
ar::HSArray{a, s, l, dc, ihm, ihd},
i::Vararg{Int, dc}) where {a, s, l, dc, ihm, ihd}
@boundscheck check_bounds(a, i...)
local linind = dot(
ihm,
ntuple(
let t = NTuple{dc, Int}(i)
i -> t[i] - ihd[i]
end,
Val{dc}()))
ar.data[linind + firstindex(())]
end
module HeterogeneousArraysWithConstAxes
using ..HeterogeneousArrays
using ..HeterogeneousArrays: check_bounds
export HSArrayWithConstAxes
# Merges tuples while preserving intra-tuple order, with indices
# determining where each element goes (which element of the resultant
# tuple comes from which source tuple).
#
# For example, if indices is:
# (1, 2, 1, 1, 2)
# and tuples is:
# ((11, 12, 13), (21, 22))
# the result is:
# (11, 21, 12, 13, 22)
function merged_tuples(::Val{indices}, ::Val{tuples}) where {indices, tuples}
(length(indices) == sum(length, tuples, init = 0)) || error("mismatched total size")
all(
pair -> ==(pair...),
zip(
ntuple(
i -> count(==(i), indices, init = 0),
Val{length(tuples)}()),
map(length, tuples))) || error("mismatched size")
ntuple(
i ->
let j = indices[i]
tuples[j][count(==(j), indices[begin:(i - 1)], init = 0) + 1]
end,
Val{length(indices)}())
end
struct HSArrayWithConstAxes{
parent_axis_bool_indices,
const_axes,
axes, size, len, dim_count,
const_axis_bool_indices,
parent_axis_indices, const_axis_indices,
parent_axes,
Parent <: AbstractHeterogeneousArray{parent_axes}} <:
AbstractHeterogeneousArray{axes}
parent::Parent
function HSArrayWithConstAxes{pabi, ca}(ar::HA) where
{pabi, ca, pa, HA <: AbstractHeterogeneousArray{pa}}
local ax = merged_tuples(
Val{ntuple(i -> Int(pabi[i]) + 1, Val{length(pabi)}())}(),
Val{(ca, pa)}())
local cabi = ntuple(i -> !pabi[i], Val{length(pabi)}())
new{pabi, ca,
ax,
map(length, ax),
prod(length, ax),
length(ax),
cabi,
(findall(pabi)...,),
(findall(cabi)...,),
pa,
HA}(ar)
end
end
parent_axis_indices(::HSArrayWithConstAxes{
pabi,
ca,
a, s, l, dc,
cabi,
pai}) where {pabi, ca, a, s, l, dc, cabi, pai} =
pai
function Base.getindex(
ar::HSArrayWithConstAxes{pabi, ca, a, s, l, dc},
i::Vararg{Int, dc}) where {pabi, ca, a, s, l, dc}
@boundscheck check_bounds(a, i...)
ar.parent[i[collect(parent_axis_indices(ar))]...]
end
end # module HeterogeneousArraysWithConstAxes
end # module HeterogeneousArrays
LayoutTrees.jl
# Copyright 2022 Neven Sajko. All right reserved.
module LayoutTrees
using ..HeterogeneousArrays
using ..HeterogeneousArrays.HeterogeneousArraysWithConstAxes
export
LayoutTree,
LinearEquation,
SystemSolution
struct Handle
v::Int
end
mutable struct HandleGenerator{first}
next::Handle
HandleGenerator{first}() where {first} = new{first}(Handle(first))
end
function (hg::HandleGenerator)()
local ret = hg.next
hg.next = Handle(hg.next.v + 1)
ret
end
count(hg::HandleGenerator{f}) where {f} =
hg.next.v - f
struct LinearEquation{F <: Real}
lin::Vector{Pair{Handle, F}}
off::F
end
LinearEquation(l::Vector{Pair{Handle, F}}) where {F <: Real} =
LinearEquation(l, zero(F))
struct SystemSolution{F <: Real}
s::Vector{F}
function SystemSolution(eqs::Vector{LinearEquation{F}}) where {F <: Real}
local min_handle = minimum(eq -> minimum(v -> v.first.v, eq.lin), eqs)
local max_handle = maximum(eq -> maximum(v -> v.first.v, eq.lin), eqs)
isone(min_handle) || error("mnbvcfghj")
local off = map(eq -> eq.off, eqs)
local ar = zeros(F, length(eqs), max_handle)
for (i, eq) in enumerate(eqs)
for v in eq.lin
ar[i, v.first.v] = v.second
end
end
new{F}(ar \ off)
end
end
Base.getindex(s::SystemSolution, i::Handle) = s.s[i.v]
# Length is the number of children.
struct LayoutTree{
length, chil_dim_count, pabi,
T <: Any,
Children <: HSArrayWithConstAxes{
pabi, ca, a, s, length, chil_dim_count, cabi, pai, cai, pa,
<:HSArray{pa}} where {pabi, ca, a, s, cabi, pai, cai, pa}}
data::T
children::Children
LayoutTree(v::T, chil::Chil) where
{T <: Any,
pabi, ca, a, s, l, dc, cabi, pai, cai, pa, ps, pl, pdc, ihm, ihd, cl,
Chil <: HSArrayWithConstAxes{
pabi, ca, a, s, l, dc, cabi, pai, cai, pa,
<:HSArray{
pa, ps, pl, pdc, ihm, ihd,
<:NTuple{pl, <:LayoutTree{cl, dc, T}}}}} =
new{l, dc, pabi, T, Chil}(v, chil)
end
struct Node{n}
# Width, height, etc.
sizes::NTuple{n, Handle}
# If the node has a single child, this equals the top padding, bottom
# padding, left padding and right padding, in the 2D case.
#
# If the node has more than one child, this is the layout padding.
padding::Handle
end
(g::HandleGenerator)(::Type{Node{n}}) where {n} =
Node(
ntuple(
let g = g
i -> g()
end,
Val{n}()),
g(),
g())
(g::HandleGenerator)(chil::Chil) where
{pabi, ca, a, s, l, dc, Chil <: HSArrayWithConstAxes{pabi, ca, a, s, l, dc}} =
LayoutTree(g(Node{dc}), chil)
const Tree = LayoutTree{len, dc, pabi, Node{dc}} where {len, dc, pabi}
# If root has no children.
root_node_equations(t::Tree{0}, ::Int, ::Type{<:Real}) =
()
# If root has a single child.
root_node_equations(t::Tree{1, dc}, dim::Int, ::Type{F}) where
{dc, F <: Real} =
(LinearEquation([
t.data.sizes[dim] => F(-1),
t.data.padding => F(2),
t.children[ntuple(
let t = t
j -> first(axes(t, j))
end,
Val{dc}())...].data.sizes[dim] => one(F)]),)
parent_children_padding_equation(
t::Tree{len, dc, pabi},
dim::Int,
::Type{F}) where {len, dc, pabi, F <: Real} =
LinearEquation(
let
cnt = size(t, dim),
first_two = (t.data.sizes[dim] => F(-1), t.data.padding => F(cnt - 1))
ifelse(
pabi[dim],
[
first_two...,
ntuple(
let t = t, dim = dim
i -> (
t.children[ntuple(
let t = t, dim = dim, i = i
j ->
let a = axes(t, j)
ifelse(j == dim, a[begin + i - 1], first(a))
end
end,
Val{dc}())...].data.sizes[dim] =>
one(F))
end,
Val{cnt}())...],
[
first_two...,
t.children[ntuple(
let t = t
j -> first(axes(t, j))
end,
Val{dc}())].data.sizes[dim] => F(cnt)])
end)
# If root has more than one child.
root_node_equations(tree::Tree{len, dc}, dim::Int, ::Type{F}) where
{len, dc, F <: Real} =
(
parent_children_padding_equation(tree, dim, F),
# Property of an n-dimensional grid:
# 1. Choose an element of the grid. Its location is defined by n
# coordinates, one for each dimension. Its shape is defined by
# n sizes, again one for each dimension.
# 2. Choose one of the n dimensions. Varying the element's
# coordinate associated with the chosen dimension keeps the
# sizes associated with the other n-1 dimensions constant.
let
har = tree.children,
axs = axes(har),
# Chosen axis
ca = axs[dim],
fi = first(ca),
ta = ca[(begin + 1):end],
naxs = ntuple(
let dim = dim, axs = axs, ta = ta
i -> ifelse(i == dim, ta, axs[i])
end,
Val{dc}()),
cinds = CartesianIndices(naxs)
ntuple(
let har = har, dim = dim, fi = fi, cinds = cinds
i ->
let
(j, k) = map(x -> x + 1, divrem(i - 1, dc - 1)),
d = ifelse(k < dim, k, k + 1),
t = cinds[j],
r = CartesianIndex(ntuple(
let dim = dim, fi = fi, t = t
i -> ifelse(i == dim, fi, t[i])
end,
Val{length(t)}()))
LinearEquation([
har[t].data.sizes[d] => one(F),
har[r].data.sizes[d] => F(-1)])
end
end,
Val{
length(ta) *
prod(map(
a -> prod(length, a, init = 1),
(axs[begin:(dim - 1)], axs[(dim + 1):end]))) *
(dc - 1)}())
end...)
function add_tree_equations!(
eqs::Vector{LinearEquation{F}},
tree::Tree{len, dc}) where {F, len, dc}
for dim in 1:dc
append!(eqs, root_node_equations(tree, dim, F))
end
let
har = tree.children,
cartesian_indices = CartesianIndices(axes(har))
for cind in (cartesian_indices[i] for i in Base.OneTo(len))
add_tree_equations!(eqs, har[cind])
end
end
eqs
end
end # module LayoutTrees
Is it perhaps possible to debug this by looking at some intermediate representation?