Can a line continue at a '.'?

Leo Dorst & Steven De Keninck have posted a tutorial and javascript code to animate a hypercube dangling from an elastic string and I’m in the process of porting that code to Julia.

The following is their code to generate the vertices of the hypercube. The first three lines generate the coordinates and the last line converts those coordinates to geometric algebra expressions.

var p = [...Array(2**d)]                                        // correct number of points
        .map((x,i)=>('000000'+i.toString(2)).slice(-d))         // convert to binary
        .map(x=>x.split('').map(v=>v-0.5))                      // split and center
        .map(x=>!(1e0 + x*[1e1,1e2,1e3,1e4,1e5].slice(0,d)));   // convert to PGA points

In my port of generating the coordinates, I’m wondering if there is a similar way in Julia to continue a line at a ‘.’ instead of having a bunch of temporary variables or a very long line as in the following code:

# wfs: physics simulation of Wire Frame on String
function wfs(nD::Int64=2)
	# generate vertices of wireframe
	BS = bitstring.(Int8.(0:2^nD-1))
	print("BS: "); display(BS)
	V = Vector{UInt8}.(BS)
	print("V: "); display(V)
	V2 = reduce(hcat,V)'[:,8-nD+1:8] .- 0x30
	print("V2: "); display(V2)
	V3 = Float32.(V2) .- 0.5
	print("V3: "); display(V3)
	
	V = (Float32.(reduce(hcat,Vector{UInt8}.(bitstring.(Int8.(0:2^nD-1))))'[:,8-nD+1:8] .- 0x30) .- 0.5f0)
end

The Noteworthy Differences from other Languages doc seems to suggest that enclosing the long expression in parentheses would allow a line to continue at a ‘.’:

Julia has no line continuation syntax: if, at the end of a line, the input so far is a complete expression, it is considered done; otherwise the input continues. One way to force an expression to continue is to wrap it in parentheses.

However, my attempts to break up this long line, even when enclosed in parentheses, have all resulted in error messages.

The dot . has several meanings in Julia, i.e., property access, broadcasting. Yet, as methods are not part of classes, method chaining in Julia is just function composition. In this sense your nested application of the last line is all it takes.
In case you don’t like that it reads inside-out instead of from-left-to-right, there are several macro solutions available, e.g., Chain.jl

using Chain

@chain 0:2^nD-1 begin
    Int8.(_)
    bitstring.(_)
    Vector{UInt8}.(_)
    reduce(hcat, _)'[:,8-nD+1:8]
    _ .- 0x30
    Float32.(_) .- 0.5
end
2 Likes

Have you tried using .|>? Seems like it should handle most of these operations.

1 Like

Thanks! No, I hadn’t even thought of trying it because I wasn’t familiar with it but I am now and it works:

julia> Int8.(0:3) .|> bitstring .|> Vector{Char} .|> (x -> x[7:8]) .|>
       (x -> parse.(Int,x)) .|> (x -> Float32.(x) .- 0.5f0)
4-element Vector{Vector{Float32}}:
 [-0.5, -0.5]
 [-0.5, 0.5]
 [0.5, -0.5]
 [0.5, 0.5]

Thanks for referencing function composition. It’s nice when dots start connecting. (I’m just starting to read about composition in Eugenia Cheng’s recent book The Joy of Abstraction - An Exploration of Math, Category Theory, and Life.)

This is a tangent, but this seems like a pretty obtuse way to generate all combinations of \pm1/2. There are more direct options you might consider.

julia> pmhalf = (-0.5,+0.5)
(-0.5, 0.5)

julia> Iterators.product(pmhalf,pmhalf,pmhalf) |> collect
2×2×2 Array{Tuple{Float64, Float64, Float64}, 3}:
[:, :, 1] =
 (-0.5, -0.5, -0.5)  (-0.5, 0.5, -0.5)
 (0.5, -0.5, -0.5)   (0.5, 0.5, -0.5)

[:, :, 2] =
 (-0.5, -0.5, 0.5)  (-0.5, 0.5, 0.5)
 (0.5, -0.5, 0.5)   (0.5, 0.5, 0.5)

If you need the contents to be vectors rather than tuples, you can collect all the individual tuples. Better yet, consider StaticArrays.

julia> Iterators.product(pmhalf,pmhalf,pmhalf) .|> collect
2×2×2 Array{Vector{Float64}, 3}:
[:, :, 1] =
 [-0.5, -0.5, -0.5]  [-0.5, 0.5, -0.5]
 [0.5, -0.5, -0.5]   [0.5, 0.5, -0.5]

[:, :, 2] =
 [-0.5, -0.5, 0.5]  [-0.5, 0.5, 0.5]
 [0.5, -0.5, 0.5]   [0.5, 0.5, 0.5]

julia> using StaticArrays

julia> Iterators.product(pmhalf,pmhalf,pmhalf) .|> SVector
2×2×2 Array{SVector{3, Float64}, 3}:
[:, :, 1] =
 [-0.5, -0.5, -0.5]  [-0.5, 0.5, -0.5]
 [0.5, -0.5, -0.5]   [0.5, 0.5, -0.5]

[:, :, 2] =
 [-0.5, -0.5, 0.5]  [-0.5, 0.5, 0.5]
 [0.5, -0.5, 0.5]   [0.5, 0.5, 0.5]

You can use the vec function if you want any of these multidimensional arrays flattened.

julia> Iterators.product(pmhalf,pmhalf,pmhalf) .|> SVector |> vec
8-element Vector{SVector{3, Float64}}:
 [-0.5, -0.5, -0.5]
 [0.5, -0.5, -0.5]
 [-0.5, 0.5, -0.5]
 [0.5, 0.5, -0.5]
 [-0.5, -0.5, 0.5]
 [0.5, -0.5, 0.5]
 [-0.5, 0.5, 0.5]
 [0.5, 0.5, 0.5]

Note that it isn’t strictly necessary to collect these arrays in the first place. You can use the Iterators.product object itself if you just need to be able to access these in a loop or something.

julia> for vertex in Iterators.product(pmhalf,pmhalf,pmhalf)
               @show vertex
       end
vertex = (-0.5, -0.5, -0.5)
vertex = (0.5, -0.5, -0.5)
vertex = (-0.5, 0.5, -0.5)
vertex = (0.5, 0.5, -0.5)
vertex = (-0.5, -0.5, 0.5)
vertex = (0.5, -0.5, 0.5)
vertex = (-0.5, 0.5, 0.5)
vertex = (0.5, 0.5, 0.5)

I am almost certain there are packages for making permutations of objects, but I don’t have one on-hand to recommend.

I think their aim was to avoid if statements in the generation of the hypercube vertices in any dimension: nD=2 is a square, nD=3 is a cube, nD=4 is a tesseract, …

No need for if statements:

vec(collect(Iterators.product(((-0.5, 0.5) for _ in 1:nD)...)))
1 Like

That’s beautiful. (score: Julia +1, JavaScript 0)

Please see Strings · The Julia Language Regular Expression.

s Treat string as single line. That is, change “.” to match any
character whatsoever, even a newline
, which normally it would
not match.


i   Do case-insensitive pattern matching.

    If locale matching rules are in effect, the case map is taken
    from the current locale for code points less than 255, and
    from Unicode rules for larger code points. However, matches
    that would cross the Unicode rules/non-Unicode rules boundary
    (ords 255/256) will not succeed.

m   Treat string as multiple lines.  That is, change "^" and "$"
    from matching the start or end of the string to matching the
    start or end of any line anywhere within the string.

s   Treat string as single line.  That is, change "." to match any
    character whatsoever, even a newline, which normally it would
    not match.

    Used together, as r""ms, they let the "." match any character
    whatsoever, while still allowing "^" and "$" to match,
    respectively, just after and just before newlines within the
    string.

x   Tells the regular expression parser to ignore most whitespace
    that is neither backslashed nor within a character class. You
    can use this to break up your regular expression into
    (slightly) more readable parts. The '#' character is also
    treated as a metacharacter introducing a comment, just as in
    ordinary code.