victor-alves
victor-alves

Reputation: 23

How to call function from within constructor of a struct in Julia?

I'm a programmer with a C++ and Python background who recently stumbled upon Julia, and I really like what it has to offer. In order to become more familiar with both blockchain implementation and Julia at the same time, I'm being a little ambitious and am trying to create a basic implementation of a blockchain in Julia by converting the Python implementation posted by Hackernoon (The author explains what each method is supposed to do better than I ever could).

However, I'm running into issues when creating the actual Blockchain struct. In order to create the genesis block, Hackernoon suggests I call the member function new_block in the constructor. So far, I haven't been able to figure out how to best replicate this in Julia. Here's what I have so far:

import JSON
import SHA

mutable struct Blockchain
    chain::Array{Dict{String, Any}}
    current_transactions::Array{String}
    block::Dict{String, Any}
    new_block::Function

    function Blockchain(chain::Array{Dict{String, Any}}, current::Array{String})    
        new(chain, current, new_block(previous_hash=1, proof=100)) 
        # ^issue calling function within the constructor here
    end
end

When I try to run my code, I get the following error:

invalid redefinition of constant Blockchain.

Here's the new_block function:

function new_block(proof::String, previous_hash::String=nothing)
    block = Dict(
    "index" => length(chain) + 1,
    "timestamp" => time(),
    "transactions" => current_transactions,
    "proof" => proof,
    "previous_hash" => previous_hash | hash(chain[end]),
    )
    current_transactions = []
    append!(chain, block)
    return block
end

Here are the rest of the functions I currently have:

function new_transaction(this::Blockchain, sender::String, recipient::String, amount::Int)
    
     transaction = Dict(
        "sender"=> sender,
        "recipient"=> recipient,
        "amount"=> amount,
     )

    append!(this.current_transactions, transaction)

    return length(this.chain) + 1
end

function hash(block::Blockchain)
    block_string = JSON.dumps(block, sort_keys=true).encode()
    return sha256(block_string).hexdigest()
end

I might have some misconceptions about how types/structs work in Julia; most of my information was obtained from third party websites along with the official documentation. Here are some of the sources I've been relying on:

Smarter/more efficient ways of trying to accomplish what I am would be immensely appreciated.

Edit:

Here are some of the changes I've made, based on given suggestions:

struct Blockchain
    chain::Array{Dict{String, Any}}
    current_transactions::Array{String}

    function Blockchain(chain::Array{Dict{String, Any}}, current::Array{String})    
        new(chain, current)
    end
end

function new_block!(this::Blockchain, proof::Int, previous_hash::Int=nothing)
    block = Dict(
    "index" => length(this.chain) + 1,
    "timestamp" => time(),
    "transactions" => this.current_transactions,
    "proof" => proof,
    )
    if previous_hash == nothing
        block["previous_hash"] = hash(this.chain[end])
    else
        block["previous_hash"] = previous_hash
    end

    this.current_transactions = []
    append!(this.chain, block)
    return this
end

I realized that the block attribute was useless, as it only existed to be added to the chain, so I removed it.

Additionally, here is an alternate Blockchain definition without the inner constructor:

struct Blockchain
    chain::Array{Dict{String, Any}}
    current_transactions::Array{String}
    
    Blockchain(x::Array{Dict{String, Any}}, y::Array{String}) = new(x, y)
end

Upvotes: 2

Views: 2517

Answers (1)

Arda Aytekin
Arda Aytekin

Reputation: 1303

DISCLAIMER. This may not necessarily be an answer to your question. But I wanted to post it as an answer, as a comment does not allow me to express the below that easily.

mutable struct Blockchain
    chain::Array{Dict{String, Any}}
    current_transactions::Array{String}
    block::Dict{String, Any}
    new_block::Function

    function Blockchain(chain::Array{Dict{String, Any}}, current::Array{String})    
        new(chain, current, new_block(previous_hash=1, proof=100)) 
        # ^issue calling function within the constructor here
    end
end

Here, I assume that you are trying to add some member function functionality to your struct, as you have already stated that you are coming from a C++ background. However, this is not Julian. In Julia, as @crstnbr has already suggested, we need to define global functions that act on objects. The convention is that you add ! at the end of the function to indicate that the function will be changing at least one of its arguments.

Then, by checking the definition of your new_block:

function new_block(proof::String, previous_hash::String=nothing)
    block = Dict(
    "index" => length(chain) + 1, # which chain?
    "timestamp" => time(),
    "transactions" => current_transactions, # which current_transactions?
    "proof" => proof,
    "previous_hash" => previous_hash | hash(chain[end]), # which chain?
    )
    current_transactions = []
    append!(chain, block) # which chain?
    return block
end

I notice a couple of serious mistakes. First, there are some undefined variables that you try to use such as chain and current_transactions. I assume, again from C++, you have thought that new_block would be a member function of Blockchain, and hence, could see its chain member variable. This is not how Julia works. Second problem is how you try to call new_block:

new_block(previous_hash=1, proof=100)

This call is totally wrong. The above call notation relies on keyword arguments; however, your function definition only has positional arguments. To be able to support keyword arguments, you need to change your function definition to read as below:

function new_block(; proof::String, previous_hash::String=nothing)
  #                ^ note the semi-colon here
  # ...
end

And last, you define proof and previous_hash to be of type String, but call them with 1, 100 and nothing, which are of type Int, Int and Void.

I could not understand your design choice for the Blockchain application in your mind, but I strongly suggest that you should go step-by-step with simpler examples to learn the language. For instance, if you just try the below examples, you will understand how type annotations work in Julia:

Main> f(s::String = nothing) = s
f (generic function with 2 methods)

Main> f()
ERROR: MethodError: no method matching f(::Void)
Closest candidates are:
  f(::String) at none:1
  f() at none:1
Stacktrace:
 [1] f() at ./none:1
 [2] eval(::Module, ::Any) at ./boot.jl:235

Main> g(s::String) = s
g (generic function with 1 method)

Main> g(100)
ERROR: MethodError: no method matching g(::Int64)
Closest candidates are:
  g(::String) at none:1
Stacktrace:
 [1] eval(::Module, ::Any) at ./boot.jl:235

Main> h1(var1 = 1, var2 = 100) = var1 + var2
h1 (generic function with 3 methods)

Main> h1(var2 = 5, var1 = 6)
ERROR: function h1 does not accept keyword arguments
Stacktrace:
 [1] kwfunc(::Any) at ./boot.jl:237
 [2] eval(::Module, ::Any) at ./boot.jl:235

One last comment is that as far as I can see from your example, you do not need mutable struct. struct should simply help you with your design --- you can still add to/modify its chain, current_transactions and block variables. Check, again, the simpler example below:

Main> struct MyType
         a::Vector{Float64}
       end

Main> m = MyType([1,2,3]);

Main> append!(m.a, 4);

Main> m
MyType([1.0, 2.0, 3.0, 4.0])

You can think of MyType's a variable, in the above example, as being double * const a in C++ terms. You are not allowed to change a to point to a different memory location, but you can modify the memory location pointed-to by a.

In short, you should definitely try to learn the language from the official documentation step-by-step, and post questions here involving really minimal examples. Your example is convoluted in that sense.

Upvotes: 2

Related Questions