How to exactly lock the type of the arguments of a function?

Another stupid question, I have a function

function gauss(beta::Array{Float64,1})
beta  .= [1.0 2.0; 3.0 3.0]
return nothing
end

I set beta in the argument as a 1D array, however I set beta in the function as a 2D array. It seems to me it should give me error.
However, now, why Julia still allow that? Shouldn’t it give me an error?

Furthermore,

if I define a 2D array,

b = [1.0 2.0 ; 3.0 4.0] 

if I do

gauss(b) 

Julia still runs it without giving error. It seems the restriction (beta has to be 1D array) I set in the argument of gauss does not work at all. Why is that?

Thanks in advance!

Type assertions in function arguments only restrict dispatch. They are not like a type assertion inside of a function or for a global variable.

You’ve asked a similar question (with a related answer from me and @Vasily_Pisarev , as well as a link to an answer by Stefan Karpinski on Stackoverflow on the difference between struct fields and dispatch) a few days ago, was it not clear enough? Have you read through the manual? If you haven’t, I really recommend doing so - it may seem long & daunting, but I think it will clear up a lot of confusion you seem to have about how julia works. It’s best to acknowledge that Julia is not Fortran but rather its own thing.

3 Likes

I get an error

julia> function gauss(beta::Array{Float64,1})
       beta  .= [1.0 2.0; 3.0 3.0]
       return nothing
       end
gauss (generic function with 1 method)

julia> b = [1.0 2.0 ; 3.0 4.0]
2×2 Matrix{Float64}:
 1.0  2.0
 3.0  4.0

julia> gauss(b)
ERROR: MethodError: no method matching gauss(::Matrix{Float64})

You do not set beta as a 2D array, you take the elements from the 2D array [1.0 2.0; 3.0 3.0] and put them into the 1D array beta. Afterwards, beta is still a 1D array. That is what the dot in .= does: it performs the assignment elementwise.

Edit: I did not see that you actually call gauss with a 2D array. In that case, it should definitely error. Are you sure you didn’t define another method that works with 2D arrays?

2 Likes

It’s likely you have another method of gauss floating around the REPL. Try methods(gauss) to check. Your type restriction should be throwing an error otherwise.

3 Likes

This is what your code should do, in a fresh session:

julia> function gauss(beta::Array{Float64,1})
           beta  .= [1.0 2.0; 3.0 3.0]
           return nothing
       end
gauss (generic function with 1 method)


julia> b = [1. 2; 3 4]
2×2 Matrix{Float64}:
 1.0  2.0
 3.0  4.0

julia> gauss(b)
ERROR: MethodError: no method matching gauss(::Matrix{Float64})
Closest candidates are:
  gauss(::Vector{Float64}) at REPL[1071]:1
Stacktrace:
 [1] top-level scope
   @ REPL[1073]:1

julia> gauss(vec(b))
ERROR: DimensionMismatch("array could not be broadcast to match destination")
Stacktrace:
 [1] check_broadcast_shape(shp::Tuple{Base.OneTo{Int64}}, Ashp::Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}})
   @ Base.Broadcast ./broadcast.jl:520
 [2] check_broadcast_axes
   @ ./broadcast.jl:523 [inlined]
 [3] instantiate
   @ ./broadcast.jl:269 [inlined]
 [4] materialize!
   @ ./broadcast.jl:894 [inlined]
 [5] materialize!
   @ ./broadcast.jl:891 [inlined]
 [6] gauss(beta::Vector{Float64})
   @ Main ./REPL[1071]:2
 [7] top-level scope
   @ REPL[1074]:1
2 Likes

By the way, if a function mutates its arguments, it is conventional to name it with an exclamation point function gauss!(b).

1 Like

Thank you very much. Those questions I asked before are a little different.

First of all, let me make it clear, I am not proud of asking such stupid questions at all.
Like Matthew McConaughey said in the Wolf of the Wall Street,

... not because I want to, but because I xxx need to. 

Here I want to make sure the argument is exact the shape of the array that I defined.
However, are you saying Julia cannot do that?
I am sure Julia can do this but I just do not know how.

My purpose is simple, I want to be strict in defining the type and shape of the array in the argument of a function, otherwise it is easy to make mistakes here and there.
For me, the way Fortran code is written is very difficult to make such mistakes.

No I have not gone through the manual too much, but I read many reddit and stackoverflow and other forums stuff before I do Julia now, my very stupid conclusion is,

  1. If a Julia code looks like Python, it performs like Python; If looks like modern Fortran, it performs like modern Fortran.
  2. Type stability is crucial.

For an Fortran outsider, and not a very gifted programmer like me, the above statements are simple and easy to understand, and easy to implement. After all, I am using Julia to do high performance computing and get my results. I am not using it to do fancy things like plots, making videos etc. I know Julia also have many fancy macros, but I current not very familiar with them either.

I learn Julia just from a very practical aspect, and I learn it completely from scratch, I do not have any one around can help and I do not have a Julia code to begin with. Otherwise I do not even need to be here, right?

All what I have to translate now, is my own modern Fortran Monte Carlo parametric EM algorithm which I am a little proud of because probably it is the fastest/robust on the market now because I use a novel method from quantum Monte Carlo. Not because I want to translate to Julia, but because I need to. Because I need to further implement differential equation here and Julia could be a good choice.

I know if I read Julia manuals I can understand a lot, but I am just not that talented at learning Julia, and that is why I come to here. Reading a language book is a little too abstract for me. It is not like reading a physics/math book which is more straightforward.

By the way, for me, learning Julia is like playing FIFA, I do not have to play easy->ammeter->semi pro->professional->world class->legendary. I just simply begin with legendary. For me, asking you guys questions is just like playing legendary level. I can afford to fail, fail, and fail, but once I won once, and I can win more.

Again, I am sorry for stupid question, by no means I am as gifted as most of you guys, but I guess I will continue to do so. My skin is thick enough and the only thing I care is to learn something form you all and get my job done.

I know the question are stupid, I got it. But I dare to ask, so that other users might not need to ask similar question. I can afford to be a stupid guy here.

Overall, I do believe for a high performance code, Julia and modern Fortran could be similar in many ways, Again,

Not because they want to, but because they xxx need to.
1 Like

Thank you very much.
Your explanation now make sense. Since I define beta::Array{Float64,1}, then beta .= [1.0 2.0; 3.0 3.0] convert the right hand side into 1D array, so it does not have error.
You are absolutely right @jzr @sostock @tomerarnon , the function name gauss is polluted, I restart the environment and it gives the error as expected.
Thank you again, the problem is solved.
Sorry for asking stupid questions.

Well, it won’t hurt to read the manual, at least the parts related to the argument passing conventions, type system, dispatch, handling of the arrays, numerical computing, performance tips and style guide. That may eliminate a large fraction of questions to begin with. It’s not a huge read, should take maybe a day or two.

Now, if you need to restrict the dimensions, the best way is to use StaticArrays.jl package. It provides array types with sizes encoded in signature. Using them must also improve performance as the standard arrays aren’t optimized for small sizes (see also this topic).

Style-wise, return nothing isn’t very Julian. If you look at the standard library functions, very small fraction of them uses nothing as the return value, even those with the main purpose of mutation (e.g. push! or copy! or sort!) return something return something that can be immediately used. In your case, return beta is the obvious choice.

Regarding a confusion from defining multiple methods and getting (or not getting) errors because the call dispatched to a stale (from your viewpoint) method. Consider Pluto.jl for prototyping. It is designed around the idea that there must not be any hidden state beside what is visible in the notebook (i.e. if you delete a method, it automagically starts a fresh process where that method is not defined). Or use Revise.jl which also keeps method tables consistent with what’s written in your source files.

2 Likes

It should error actually. See the second error that comes up in my last comment (also hidden below). That is a “dimension mismatch error” that is the result of trying to assign a matrix into a vector with a broadcast. It can’t be done that way. You can, for example, use copyto! to do that though.

If you are now seeing it work, when the beta passed in is a Vector, then something is wrong again.

error
julia> b
2×2 Matrix{Float64}:
 1.0  2.0
 3.0  4.0

julia> vb = vec(b)
4-element Vector{Float64}:
 1.0
 3.0
 2.0
 4.0

julia> vb .= b
ERROR: DimensionMismatch("array could not be broadcast to match destination")
Stacktrace:
 [1] check_broadcast_shape(shp::Tuple{Base.OneTo{Int64}}, Ashp::Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}})
   @ Base.Broadcast ./broadcast.jl:520
 [2] check_broadcast_axes
   @ ./broadcast.jl:523 [inlined]
 [3] instantiate
   @ ./broadcast.jl:269 [inlined]
 [4] materialize!
   @ ./broadcast.jl:894 [inlined]
 [5] materialize!(dest::Vector{Float64}, bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{2}, Nothing, typeof(identity), Tuple{Matrix{Float64}}})
   @ Base.Broadcast ./broadcast.jl:891
 [6] top-level scope
   @ REPL[1081]:1

I strongly agree. I expect you will hear this suggestion a lot, since probably most people answering questions have read the manual and found it helpful. It’s quite an easy read as well.

3 Likes

this may seem like a lot, until you realize the manual is most just 1-3 pages per section and really covers 80% of things (thx to non-bloated Base and array interface). Manual != Docs, manual is really short!!!

2 Likes