Blog

Julia Local Packaging

So I wanted to move all my common Julia code to a support dir. My filesystem has for 30+ years contained:

$HOME/
    Code/
        CodeC/
            foo/
                src/
                    bar.c
        CodeJava/
            foo/
                src/
                    com/
                        mdh/
                            bar/
                                Quux.java
        et fucking cetera

Build scripts for most languages expect something very like this, and it's easy to import one package's source into another, so I could put common code in a "Marklib" project, and get work done.

Making this happen in Julia was a lot more difficult. With a little help from Slack I made sense of the terrible package documentation for Julia and the incomprehensible errors, and wrote a script juliaMakePackage.zsh:

#!/bin/zsh
if [[ $# -ne 1 ]]; then
    echo "Usage: juliaMakePackage.zsh NAME"
    exit 1
fi
name=$1
devdir=$HOME/Code/CodeJulia
cd $devdir
julia -E "using Pkg; Pkg.activate(\".\"); Pkg.generate(\"${name}\")"
cd $name
git init
git add .
git commit -m "Initial commit"
cd ..
julia -E "using Pkg; Pkg.develop(PackageSpec(url=\"${devdir}/${name}\"))"

And then added the main dir and all packages I make to ~/.julia/config/startup.jl:

# startup.jl

push!(LOAD_PATH, pwd())
push!(LOAD_PATH, "$(homedir())/Code/CodeJulia")
push!(LOAD_PATH, "$(homedir())/Code/CodeJulia/Marklib")

println("READY $(pwd())")

Now finally I can:

% julia
READY /Users/mdh
julia> using Marklib
julia> Marklib.greet()
Hello World!
julia> 

And from there start putting in my libs. Each one needs a package and a startup entry; I may have to automate that by walking my code dir. Waste of several hours figuring that out.

Lovely H.P. Lovecraft Day

Below, one of my favorites to curl up and enjoy; "The Book" fragment elaborates on the first few sections, but the poetic rewrite is more effective:

Fungi from Yuggoth, by H.P. Lovecraft:

I. The Book

The place was dark and dusty and half-lost
In tangles of old alleys near the quays,
Reeking of strange things brought in from the seas,
And with queer curls of fog that west winds tossed.
Small lozenge panes, obscured by smoke and frost,
Just shewed the books, in piles like twisted trees,
Rotting from floor to roof—congeries
Of crumbling elder lore at little cost.

I entered, charmed, and from a cobwebbed heap
Took up the nearest tome and thumbed it through,
Trembling at curious words that seemed to keep
Some secret, monstrous if one only knew.
Then, looking for some seller old in craft,
I could find nothing but a voice that laughed.

II. Pursuit

I held the book beneath my coat, at pains
To hide the thing from sight in such a place;
Hurrying through the ancient harbor lanes
With often-turning head and nervous pace.
Dull, furtive windows in old tottering brick
Peered at me oddly as I hastened by,
And thinking what they sheltered, I grew sick
For a redeeming glimpse of clean blue sky.

No one had seen me take the thing—but still
A blank laugh echoed in my whirling head,
And I could guess what nighted worlds of ill
Lurked in that volume I had coveted.
The way grew strange—the walls alike and madding—
And far behind me, unseen feet were padding.

III. The Key

I do not know what windings in the waste
Of those strange sea-lanes brought me home once more,
But on my porch I trembled, white with haste
To get inside and bolt the heavy door.
I had the book that told the hidden way
Across the void and through the space-hung screens
That hold the undimensioned worlds at bay,
And keep lost aeons to their own demesnes.

At last the key was mine to those vague visions
Of sunset spires and twilight woods that brood
Dim in the gulfs beyond this earth’s precisions,
Lurking as memories of infinitude.
The key was mine, but as I sat there mumbling,
The attic window shook with a faint fumbling.

IV. Recognition

The day had come again, when as a child
I saw—just once—that hollow of old oaks,
Grey with a ground-mist that enfolds and chokes
The slinking shapes which madness has defiled.
It was the same—an herbage rank and wild
Clings round an altar whose carved sign invokes
That Nameless One to whom a thousand smokes
Rose, aeons gone, from unclean towers up-piled.

I saw the body spread on that dank stone,
And knew those things which feasted were not men;
I knew this strange, grey world was not my own,
But Yuggoth, past the starry voids—and then
The body shrieked at me with a dead cry,
And all too late I knew that it was I!

continued

Monday Note

"The social network is built on values that are so shady that it can’t be trusted to address fake news issues. Some countries already suffer from it."
—Frederic Filloux

Normally I read Monday Note for the inimitable Jean-Louis Gassée's posts, like 50 Years in Tech, Part 1 and Part 2 largely about HP; as a former HP-er during Carly's disastrous reign of terror, it's fascinating to read about an HP that wasn't on fire and screaming.

But this time Frederic, the news guy, is actually posting something of interest, and you should read those Fake News posts.

Anatomy of Frank Herbert

Truthfully, "God Emperor of Dune" is the best book in the series, but you have to read the second and third books to get to it. And they are… They are just the worst.
—Dave Kellett

This is completely true. We can theorize about how Chapterhouse: Dune or the final book might've been the best if Frank hadn't been dying, but certainly the abominations perpetrated by Kevin J. Anderson (worst writer in the world) and Frank's incompetent son Brian were not that.

But the Dune series isn't Frank Herbert's best work. I'd put at least Destination: Void, Whipping Star, Dosadi Experiment, Hellstrom's Hive, and Eyes of Heisenberg above it, both for scope of ideas, character development, and pacing.

Dune most of the time meanders aimlessly through the desert, eventually coming up with a memorable scene, and Dune Meshuggeneh (#2) and Super-Babies of Dune (#3) are the least interesting books Herbert ever wrote. The others are all far more tightly written.

His short story collections, mostly about the ConSentiency setting (I want a chairdog!), are fantastic. And for sequels, the Bill Ransom co-authored Jesus Incident, Lazarus Effect, Ascension Factor are good stories of Humans under a deranged AI that thinks it's a god: Good lessons for the coming times.

Julia String Concatenation

Last time, I was uncertain about string concatenation, so I did a test:

#!/usr/bin/env julia

const kTestText = "abcdefghijklmnopqrstuvwxyz0123456789\n"
const kLoops = 10000

function stringString()
    s = ""
    for i in 1:kLoops
        s = "$s$kTestText"
    end
    return s
end

function bufferString()
    sb = IOBuffer()
    for i in 1:kLoops
        print(sb, kTestText)
    end
    return String(take!(sb))
end

function vectorString()
    sb = Vector()
    for i in 1:kLoops
        push!(sb, kTestText)
    end
    return join(sb, "")
end

function typedVectorString()
    sb = Vector{AbstractString}()
    for i in 1:kLoops
        push!(sb, kTestText)
    end
    return join(sb, "")
end

println("*** stringString")
@timev stringString()
sleep(2)

println("\n*** bufferString")
@timev bufferString()
sleep(2)

println("\n*** vectorString")
@timev vectorString()
sleep(2)

println("\n*** typedVectorString")
@timev typedVectorString()

*** stringString
  1.100197 seconds (21.24 k allocations: 1.725 GiB, 15.94% gc time)
elapsed time (ns): 1100197167
gc time (ns):      175349222
bytes allocated:   1851904041
pool allocs:       11292
non-pool GC allocs:9950
GC pauses:         79

*** bufferString
  0.006864 seconds (11.80 k allocations: 1.134 MiB)
elapsed time (ns): 6864042
bytes allocated:   1189493
pool allocs:       11794
non-pool GC allocs:3
realloc() calls:   8

*** vectorString
  0.017380 seconds (26.68 k allocations: 2.191 MiB)
elapsed time (ns): 17380237
bytes allocated:   2297091
pool allocs:       26659
non-pool GC allocs:9
realloc() calls:   8

*** typedVectorString
  0.031384 seconds (44.75 k allocations: 2.999 MiB, 10.00% gc time)
elapsed time (ns): 31384383
gc time (ns):      3137654
bytes allocated:   3144221
pool allocs:       44730
non-pool GC allocs:8
realloc() calls:   8
GC pauses:         1

Well, there's me told off. I expected #1 typedVector, #2 vector, #3 buffer, then stringString way at the bottom. Instead the first 3 are reversed.

IOBuffer, as ugly as it is, is the clear winner. Vector did OK, but twice as much CPU & RAM loses. Amusing that typedVector is twice as slow and memory-heavy as the untyped (explained ). On larger loops, buffer gets slower, but vector remains a memory pig, and in GC that's unacceptable. Of course stringString is terrible, and it's almost exactly the same for string(s, kTestText).

Time to rewrite some text processing.

Twitpocalypse Now

The big winners of this so far have been ActivityPub servers, especially Mastodon, and micro.blog, where I've seen a lot of people finally jump out of the boiling pot (I was gonna say "frogs" instead of "people", but the whole right-wing frog avatar thing…). My handles are in that About page above you, if you want to follow.

If you're picking an ActivityPub instance, be aware that mastodon.social is a giant possibly-hostile mess like Twitter, and not really a "community" like many other instances. Pick a smaller instance, read the timeline on their instance's front page, and make a more informed choice. You can communicate with almost everyone in the Fediverse and see a similar Federated timeline from almost any instance, but the Local timeline will be different.

If you were on ADN, you can ask me for an mdhughes@appdot.net invite. Pleroma is also interesting, and might be more to your taste.

Anyway, welcome to the free world, ex-twitterers!

More Julia

Decided to take another day on Julia, write something more serious and see how that goes.

There's an uber-juno "IDE" plugin for Atom, which at least turns on syntax highlighting and puts an interactive console in the editor. Yay. It's not capable of linting yet, though it says it is.

So I'm rewriting a simple 1970s-style dungeon crawl game (Chorus:"As if you could make any other kind of game, Mark!" Mark:"I assure you I could, I just choose not to.") as a test of data structures and application programming in Julia. It's a little challenging, but not impossible.

Using Modules

The current directory is not included in the default LOAD_PATH, so you can't import local modules right off. One solution is to put it in your startup:

% mkdir -p ~/.julia/config
% echo '@everywhere push!(LOAD_PATH, pwd())' >>~/.julia/config/startup.jl

All names are in the same namespace. This means your module and a struct or method in it can't have the same names… My current solution has been to pluralize the module, so GridMaps contains a struct GridMap.

The export rules are a little annoying. If you use the @enum macro to make a ton of constants, they aren't exported when you export the enum type; you can either manually export each constant name, or just use ModuleName.ConstantName in other modules. Bleh.

Debugging

Bug #1: The terminator problem is pretty bad in Julia:

% cat Foo.jl
module Foo
for i=1:10
    println(i)
#missing end
end #module

% julia Foo.jl
ERROR: LoadError: syntax: incomplete: "module" at /Users/mdh/Code/CodeJulia/Foo.jl:1 requires end
Stacktrace:
 [1] include at ./boot.jl:317 [inlined]
 [2] include_relative(::Module, ::String) at ./loading.jl:1038
 [3] include(::Module, ::String) at ./sysimg.jl:29
 [4] exec_options(::Base.JLOptions) at ./client.jl:229
 [5] _start() at ./client.jl:421
in expression starting at /Users/mdh/Code/CodeJulia/Foo.jl:1

Good luck finding that missing end if you have a 1000-line module. C used to be just as bad about semicolons and braces, but modern compilers are pretty good at guessing where you fucked up. Python's whitespace-as-control is brilliant, because you can't ever do that. A passable solution would be each control keyword having its own unique end keyword, but it's too late for that. In the actual bug, I had to comment out half the code, run the module, uncomment and comment the other half, repeat until I isolated it.

Bug #2: Type annotations need to be very generic or left off entirely. As code-as-documentation, I declare a function as parseLine(line::String), and it gives me:

MethodError(Main.Foo.parseLine, ("a",), 0x00000000000061bb)

Well, thanks. Turns out I need to use AbstractString, because a prior function returns an AbstractString and not String. Or I can just leave the typing off, as was my first instinct.

String Concatenation

This is super ugly. Currently I've fallen back on:

sb = Vector()
push!(sb, "part of ")
push!(sb, "a string")
return join(sb, "")

Strings aren't mutable, and there's no StringBuffer/NSMutableString equivalent. The other option is to use an IOBuffer. I haven't done timings yet to see which is faster/uses less memory, I just find pushing to a vector simpler.

Switch

There's no switch statement. I could put functions in a dictionary and dispatch on that, which is not ideal for looping over a bunch of simple values, and requires passing around control flags instead of a simple break or return. Caveman solution is a chain of if/elseif, but I don't like it. Possibly a macro could be written?

Binary

Turns out you can make binaries: Julia apps on the App Store: Building and distributing an application written in Julia

It's an ugly process, that Nathan's working around, but it's a start. This should 100% be in the core libraries, and should have a cross-compiler.

Getting this working is tomorrow's problem, I think.