Creating an Accounting Program in Julia, for Pluto

You can println your error message, and return false.
Or you can return an Integer, 0 for no error, 1,2,3,… for all the errors which you handle.
These are thoughts which can be dependend on the future use of this function.
For now, choose what’s easiest for you (but type stable).

Don’t forget issue “global variables”.

Print is a great solution, unfortunately Pluto doesn’t show printed messages, not as big a fan of the number idea, but it would indeed work.

I haven’t forgot about the global varible issue, and I think it’s a better issue, but I want to get this sorted first.

I see, I didn’t check in Pluto.
So, you can return a tuple of bool and string: (false,“my error”) in case of an error, and (true,"") or (true,“no error”) in case of no error.

I need to look up bools, I have a vauge idea on how to write a bool, but I’m not entirely familiar with the types.

I would define the date field of type Date, Dates as strings are a nightmare (no validation, bad performance, etc.).
When you initialize the DataFrame this way, you create one (probably invalid) entry. It would be better to initialize it empty (but with typed columns):

ledger = DataFrame(date=Date[], chequing=Int[],	expenses=Int[],	income=Int[])

A bool has just two values: true or false. You can also use integer 0 and 1. Doesn’t matter to much. Important: always returning the same type, e.g. (1,“error”) which is a Tuple{Int,String}.

Yes, you are right.
I want the function ledgertransaction to be agnostic of these details (at least a bit agnostic, of course some logic must be implemented). But dealing with Date-types at this point is quite a step, so I would postpone it for later.

I wanted to have an abbreviation for date, instead of numbers, as I feel this can create confusion. I will look through the documentation for Date.jl, and get this set up properly.

Good.

I think check is the easiest thing to start

check = bool{chequing_amount - debit_amount!=0,“credit and debit amounts must match”}

You are thinking too complex here. It’s easy like:

function ledgertransaction!( ledger, date, credit_account, credit_amount, debit_account, debit_amount )

    if credit_amount-debit_amount != 0
        return (false, "Error -- credit and debit must match.")
    else if length(date) == 0
        return (false, "Error -- no date entered")
    else ...

    ...
    return (true, "no error)
end

Yes, I tend to do that lol. This solution works great.

I believe the next issue you said is a priority is with global and local variables?

Yes, I already proposed it with:

function ledgertransaction!( ledger, date, credit_account, credit_amount, debit_account, debit_amount )

Read about it using the docs (link above).
If you confirm, provide the complete function we have now, same if you not confirm.

Is this a new function that creates my initial dataframe? I’m not sure I follow from the documentation.

Just go ahead and show what you have. We will clean it up if necessary.

ledger=DataFrame(
    date=[],
    chequing=[],
    expenses=[],
    income=[]);

function ledgertransaction(
    date,
    credit_account,
    credit_amount,
    debit_account,
    debit_amount
)
	
	check=credit_amount-debit_amount
	
	if length(date) == 0
		return(false,"Error-- no date entered")
	else
	
	
	if !( credit_account in names(ledger) )
		  return (false, "Error -- credit account not found.")
	else
	
		if !( debit_account in names(ledger) )
			return (false, "Error--debit account not found")
		else
	
			if check != 0
				return (false,"Error -- credit and debit must match." )

			else

		
    default_row=Dict("chequing"=>0,"expenses"=>0,"income"=>0)
    spec = Dict(
			"date" => date, 
			credit_account => credit_amount, 
			debit_account => debit_amount)
		
    row = merge(default_row, spec)
    
	push!(ledger,row)
	
	journal_entry=DataFrame(
		                   date=["Jan 1, 2000","","",""],
		                   credited_account=["", credit_account,"",""],
						   debited_account=["","",debit_account,""],
		                   credit=[0,credit_amount,0,0],
		                   debit=[0,0,debit_amount,0],
			               
							#balance=[0,
							 #      sum(credit_account),
				              #     sum(add_debit_account),
				               #    0]
							#I need to figure out how to calculate my balance
		)

 
				end
			end
		end
	end
end

No, not yet.

  • journal_entry we don’t want yet, first steps first
  • default_row should be named default_values
  • using elseif instead of nested else if makes it more readable and less ends to use.

If you want to take a peek:

using DataFrames

ledger=DataFrame(
	date=[],
	chequing=[],
	expenses=[],
	income=[]);

function ledgertransaction(
	date,
	credit_account,
	credit_amount,
	debit_account,
	debit_amount
)
	if credit_amount-debit_amount != 0
		return (false,"Error -- credit and debit must match.")
	elseif length(date) == 0
		return (false,"Error -- no date entered")
	elseif !( credit_account in names(ledger) )
		return (false, "Error -- credit account not found.")
	elseif !( debit_account in names(ledger) )
		return (false, "Error -- debit account not found")
	else
		default_values=Dict("chequing"=>0,"expenses"=>0,"income"=>0)

		spec = Dict(
			"date" => date, 
			credit_account => credit_amount, 
			debit_account => debit_amount)
		row = merge(default_values, spec)
		push!(ledger,row)
		
		return (true, "no error")
	end
end

This is, what we want.
But still issue “global variables” is not adressed! I explain what it means:

You are using the DataFrame ledger inside the function. But it is defined outside of the function. This works, because in Pluto (as in the REPL) ledger is implicitly global. This is not the case in modules e.g., this is called soft scope. For details about scope see Scope of Variables · The Julia Language

Example in a new REPL:

julia> function test()
          println(i)
       end

julia> test()
ERROR: UndefVarError: i not defined

julia> local i =5
5

julia> test()
ERROR: UndefVarError: i not defined

julia> global i = 5   #works without global in REPL
5

julia> test()
5

Our code we develop should work in REPL, Pluto or in a module or script without any changes. It should be as generic as possible. And we want to avoid anything which decreases our performance. The docs say, we should avoid globals. Even we don’t know why, we do it, because it’s easy to avoid, it doesn’t cost anything, it’s just better.

So, what do you suggest to avoid global ledger? Hint: ledger itself in our Pluto scenario is always global (or we put a local before the definition), but we want our function ledgertransaction not to rely on that. We want to have a working ledgertransaction even if ledger is not global!

1 Like

OK, I changed the default_row to default_values, and fixed the if statements. I’ll read this over about variables.

I was over thinking again. I didn’t realise that you were saying ledger should be declared global.

global ledger=DataFrame(
    	date=[],
    	chequing=[],
    	expenses=[],
    	income=[]);

function ledgertransaction(
    date,
    credit_account,
    credit_amount,
    debit_account,
    debit_amount
)
	
	check=credit_amount-debit_amount
	
	if length(date) == 0
		return(false,"Error-- no date entered")
	
	elseif !( credit_account in names(ledger) )
		
		return (false, "Error -- credit account not found.")
	
	elseif !( debit_account in names(ledger) )
		return (false, "Error--debit account not found")
		
	elseif check != 0				
		return (false,"Error -- credit and debit must match." )

	else
	
    		default_values=Dict("chequing"=>0,"expenses"=>0,"income"=>0)
    		spec = Dict(
				"date" => date, 
				credit_account => credit_amount, 
				debit_account => debit_amount)
		
    		row = merge(default_values, spec)
    
			push!(ledger,row)
        end
end

Well, I said the opposite: You should use it as it were local!

You missed two other things from my code:

  • the return value in the case of no error
  • calculating check isn’t necessary, check what I provided (clicking on Details).