For what reason do you want to know more. Just for the sake of knowing or is there any actual code that you write that depends on knowing this?
As a user, arguably you almost never really have a need to know, unless you are interfacing with C.
Abstractions are a programmer’s best friend. Also, underspecifying a lot of low-level details in the exposed API leaves room for optimizations and refactoring. As pointed out multiple times in this topic, a value of some type may not even exist in memory as such, depending on compiler optimizations.
I was making a lot of basic mistakes in the first few weeks, trying to stretch C/C++ memory model to understand what is going on. By memory model, I don’t mean implementation details. I’m looking for something less fuzzy than label and box, whatever that means. Perhaps some sort of abstract machine tying up variables with bytes without referring to other languages.
And abstraction is what I’m looking for, not a byte layout, although I needed that too for C interface. I would like something a bit more formal than structs
are just a pile of labels.
Okay, something like “a = expression
makes a
evaluate to the object expression
is evaluated to within the scope a
is valid” maybe? Do you have a particular piece of code that is confusing?
Here is an example I just run into, which I can’t parse using a = expression
makes a
evaluate to the object expression
is evaluated to within the scope a
is valid rule. I’m expecting a behavior similar to:
a = b = 42
where a
and b
evaluate to the same value, but in this code snippet:
using AbstractPlotting, GLMakie, AbstractPlotting.MakieLayout, Random
scene, layout = layoutscene(padding = 4, resolution = (1200, 1000));
ax1 = layout[1, 1] = LAxis(scene, title = "Lines")
lines!(ax1, randn(10)) # this works
lines!(layout[1, 1], randn(10)) # this doesn't work
display(scene)
I see different behavior than ax1
and layout[1, 1]
evaluating to the same expression.
layout[1, 1] = ...
is not a normal assignment, it is syntax for setindex!(layout, 1, 1)
which is an overloadable function and can do absolutely whatever. Same with layout[1, 1]
which is getindex(layout, 1, 1)
. So there is no deductions you can make syntaxwise from that (more than that ax1
will refer to whatever object layout[1,1]
returned at that particular call).
But this:
b = zeros(3)
a = b[2] = 42
uses setindex
and behaves as expected. Are you saying that library writer overloaded setindex
, so it behaves in an unusual way? What is the way to find out whether the overload will behave like an array assignment or not?
I’m saying they could and since we are talking about what the syntax guarantees, that’s what is interesting.
Some sleuthing reveals that, yes, layout
has a custom setindex!
https://github.com/jkrumbiegel/GridLayoutBase.jl/blob/6734d2ad247361261b343975fe36a7c6604de8fb/src/gridlayout.jl#L1033
It took a bit of searching with JuliaHub to find this out, but in general, the way you could do it simply at the repl is using the @which
, or @less/@edit
macros. E.g.
julia> @which a[1] = 2
setindex!(A::Array{T,N} where N, x, i1::Int64) where T in Base at array.jl:766
Just to be clear, my analogy is intentionally hazy.
My focus was: create a mental model using simple images that actually describe the semantics of Julia memory model. It intentionally left out some details that may be important for critical performance, but not to reason about the correctness of the code.
I think it gives some important intuitions:
- Names/labels are just a way to reference something that already exists independently of the label.
- As labels are external to the object itself, giving new labels or losing old ones never modify or create a new object. If you lose all labels the object may be removed from memory, but then you do not know this, because you cannot reference it anymore.
Also, I do not have to go back and change the analogy to include mutable
and immutable struct
s. Everything I said still stands, but in mutable piles you can replace a label in the pile by another label, while immutable structs are piles in which the labels are superglued when created, so you cannot replace a label without throwing away the whole pile and creating a new one.
I can tell you why I implemented it like this if that helps. The problem is that a grid layout seems to be a matrix-like container at first glance. But you can place objects across multiple rows and columns and you can place multiple objects at overlapping locations. That means there is no clear mapping from layout[i, j] to an object at that position in the layout.
The getindex syntax returns a GridPosition object describing the position you queried instead, because such an object can be a useful thing to feed to other functions, rather than directly returning an array of objects that match the queried position in the layout.
That’s why ax1!= layout[1, 1]
. The syntax is just too useful not to use it, even though the underlying logic is different than that of normal matrices.
You can retrieve a vector of objects at a layout position with contents(layout[rows, columns])
.
Sorry for a delay, but this is another puzzle I don’t understand given vague definitions of fundamental concepts: I thought I understood "pass-by-sharing" in Julia until I found this
You are not passing anything to the function, you have captured (closed over) a local variable. Look up the docs for closures for that.
A closure is simply a callable object with field names corresponding to captured variables explains existence of fields, but makes validity and semantics of captured variable outside its scope puzzling. Is there more documentation of closures than in Julia Functions · The Julia Language ?
You can still think a variable is a Box even in Python. Why ?
I’ll apply the words: “memory address” and “id” as interchangebly.
Everything in python is an object (Except keywords). Object is also a box(id, value, type) without a name attached to it. If you want to create a name for that box(object), you has to define variable. That variable will contain the memory address(id) of that object, or another word it’s a reference(id) to a specific object. It might be different from another programming language, variable is a box labeled with a name that contains a value(Not memory address).
In Python, when write:
[1, 3, 4]
{“key”: “value”}
set()
4
“string”
id(“string”)
id( {“key”: “value”})
“”"
All the expressions above have id
“”"
Box = [1, 3, 4] # Box is a box that contains memory address of [1, 3, 4] object (not a sequence of value).
In python, generally, all the values are all objects, since there’re no names attached to the objects(things). Python really used the values to represent the objects.
But two objects are identical based on id not values. Because there’re mutable and immutable objects.
Always keep in mind, keep in mind that! ID is a priority of everything in Python. When you try to access anything, Python always uses the id of that particular thing, in the first step.