Implementing Singleton design pattern

Hi. I’m having trouble trying to implement the Singleton design pattern. In Java or C++, I would use a static pointer to the single instance, but Julia doesn’t have static data that I’m aware of. So I tried using an external variable (ugh!) like this:

REPOSITORY = nothing

mutable struct Repository
	nrows::Integer			# Max number of rows that can be drawn.
	ncols::Integer			# Max number of cols that can be drawn.
	rep						# Repository of random numbers in [0,1)

	function Repository()
		if REPOSITORY === nothing
			REPOSITORY = new(0,0,[0])
		end
		REPOSITORY
	end
end;

However, the compiler won’t accept within the Repository constructor that REPOSITORY exists. I’m assuming I need to use some kind of deferred constructor, but I don’t know how. Searches on the net for Singleton and Julia all turn up fieldless structs - interesting and singleton, certainly, but not what I’m looking for.

Look how ‘nothing’ or ‘missing’ are implemented. They are effectively singletons.

missing and nothing are different to what OP wants to achieve, since neither of them have any fields. E.g. Missing is declared as struct Missing end, which automatically makes it a singleton since Missing is immutable and has no fields, so there’s nothing that could be different between different instances.

when you say “singleton”, are you referring to limiting your struct to only allow a single instance of it to exist globally at a time? While possible to enforce, it’s rather cumbersome (and makes the required synchronization obvious & necessary). If you’re coming from OOP, there may be a better way to do what you’re trying to do if you can tell us a bit about what it is you’re trying to achieve in the grand scheme of things.

That said, you’d use a RefValue for this specific question out of context:

mutable struct Repository
    x::Int
    y::Int
    rep


   function Repository(x,y)
      # may need atomic access or a lock to make writing threadsafe
      if !isdefind(REPOSITORY, 1) 
          REPOSITORY[] = new(x,y,[0])
      end
      REPOSITORY[]
   end
end
const REPOSITORY = Base.RefValue{Repository}()

You have to declare REPOSITORY after Repository since it references the type. Since REPOSITORY is now a Ref, the compiler doesn’t try to inline or reference anything outside, since it will have to be fetched at runtime (and thus there’s no compiler error).


Still, this should only be the very last resort. If your Repository is not supposed to exist more than once anyway, why not make it immutable and just pass it around (or just pass it around anyway)? If you want to modify those fields dynamically, you may just as well create a new one instead (that’s the whole point of them).

3 Likes

Thanks for the thoughtful reply. The code you’ve written is exactly what I want to do, and at present I don’t see how my requirements could be achieved using multiple immutable instances. However, I’m also aware that I’m still coming to terms with Julia’s use of immutability and dynamic typing as an alternative to OOP, so what I say might be clouded by that. Here goes …

I’m trying to develop a new kind of genetic algorithm, and so I frequently need to generate 2-D tables of random numbers. To accelerate this generation, I create a singleton repository containing a table around five-times too big for current requirements, then generate the required table from random positions within the repository table.

Now, every so often, my GA needs a bigger table than anything used so far. In that case, I wish to reallocate the repository table in the new, bigger size. Also, occasionally I’ll want to reseed the repository table to shuffle the numbers a bit.

So: According to my current idea, the repository should be singleton, but containing an expandable table of random numbers. Alternative implementation ideas very gratefully received! :grinning:

You could try:

using Memoize

mutable struct Repository
	nrows::Integer			# Max number of rows that can be drawn.
	ncols::Integer			# Max number of cols that can be drawn.
	rep						# Repository of random numbers in [0,1)
	@memoize Repository() = new(0,0,[0])
end
1 Like

Mm - yes, that’s definitely worth a thought. Thanks!

Could you tell me a little bit more about what you mean by “generate the required table from random positions within the repository table”? Are you talking about subarrays of your random matrix, random rows, random columns…? It also sounds like there is more state that you haven’t put into the struct you’ve shown here so far. I’m not sure nrows and ncols necessarily have to be associated with the repository (is that extra metadata specific to the wrapped matrix or is it just relevant for creating the child table? If it’s the latter, why save it with the wrapped matrix at all?).

Ok, but for that the struct containing your array doesn’t necessarily have to be mutable. There’s resize! as well as rand!, for modifying your array in-place (it’s a little more tricky if your array is a proper matrix).


In general, julia’s structs should be more often than not treated like “dumb data stores”, not like classes in OOP. If it’s not data that has to be stored together to give it any semantic meaning at all, don’t store it! Pass it around with function arguments instead, since one of the core concepts in julia is that data (structs) and code (functions/methods) are not coupled like they are in java or class-based C++.

E.g., to me it sounds like your algorithm should look something like this, where you have a function that generates a matrix of the requested size from the given repo:

function newGeneration(repo, ncols, nrows)
      newGen = similar(repo, (ncols, nrows)) # create a new generation of size ncols x nrows with the same element type as repo

      for i in eachindex(newGen)
           newGen[i] = rand(repo) # pull a random element out of repo
      end

      newGen
end

As well as a way to reseed and grow that repo:

using Random
reseed!(repo) = rand!(repo) # function to fill repo with random values

function grow(repo, ncols, nrows)
      @assert ncols >= size(repo, 1) && nrows >= size(repo, 2)
      newRepo = rand(ncols, nrows)
      newRepo[begin:lastindex(repo)] .= repo
      return newRepo
end 

You’d then create your “repo” with a direct call to rand(n,m), use that in your main loop and occasionally grow a new one:

function main(generations, initCols=100, initRows=100)
    repo = rand(initCols, initRow)
    for _ in 1:generations
        curGen = newGeneration(repo, 10, 10) # use appropriate values
 
        # do testing if the generation is any good, pull out more generations etc
        # bestGens = ...

        if growRepo # determined during the above
            repo = grow(repo, ncols, nrows)
        end
        if reseed # also determined during the above
           reseed!(repo)
        end
    end

    # return best generations?
end

Sukera, thank you very much! Your post contains so many interesting new thoughts that I’m going to take a while to process it - especially as I’m having to work on several other projects simultaneously. I didn’t know anything about rand!(), which will clearly be useful for reseeding. As far as I can make out, resize!() applies only to Vectors, but I’ll look into that. In particular, as I said, I am still learning the new multiple dispatch mentality associated with Julia programming, and I find your thoughts on the (non-)coupling between data and operations very useful for my learning.

My problem with your newGeneration code is that it costs too many random generations per created table. The algorithm I’m going for at the moment instead only needs to generate four random numbers (offset and stride per dim) in order to draw a table of arbitrary size from the Repository:

function draw( repository::Repository, nrows, ncols)
	# Choose random offsets and strides for drawing on the repository:
	reprows, repcols = size(repository.vault)
	offset_r = rand( 1 : (reprows-nrows))
	stride_r = rand( 1 : (reprows-offset_r) ÷ (nrows-1))
	offset_c = rand( 1 : (repcols-ncols))
	stride_c = rand( 1 : (repcols-offset_c) ÷ (ncols-1))

	# Display a randomly chosen sub-table from the vault:
	@view repository.vault[
		(offset_r : stride_r : (offset_r + (nrows-1)*stride_r)),
		(offset_c : stride_c : (offset_c + (ncols-1)*stride_c))
	]
end

Here, vault is the matrix contained within the singleton Repository, and as you can see, there is a maximum number of rows and cols that can be drawn from any given size of Repository, which is why they are packaged together with the matrix to check whether this particular draw() operation will need to resize the matrix. The above version of draw() assumes that this check has already been performed and the vault appropriately resized.

No worries, it was just an example, not meant to be put into production! I’m glad that it can provide some insight into how to decouple this. :slight_smile: I agree that only saving ranges is much better memory allocation wise, I just wasn’t sure what you meant with “subsets of the table”.

Thank you, that’s what was missing from my understanding! Querying the size of a matrix is O(1) in julia since it’s saved as part of the object (unlike C, where you have to pass the size around manually or count elements until you reach some sentinel value, which is O(n)), so saving that size and having to maintain consistency is redundant :slight_smile:

Under the hood (sadly veering into C land, since that’s where Arrays are implemented right now), size(a, 1) calls Base.arraysize(a, 1), which is a builtin and can be found at src/builtins.c:1182. This just returns the size of a given dimension:

return jl_box_long((&a->nrows)[dno-1]);

Yes, I guess my version with @assert effectively assumes the same thing, though it wouldn’t matter much since checking the size is cheap. The reason I passed rep/vault directly into my drawing function was because other than nrows/ncols (which seemed redundant), there was no other state. If there is, of course collecting that into a struct is a good idea.

1 Like