"Hence, the development workflow is to start the process, get a cup of coffee while it loads for the first time and keep the process alive for the rest of the day". – Or take another cup of coffee each time you change structure definition…
There’s proto structs package and revise for that right?
Hmm, how many times do I change the definition of the structure? If I keep changing the structure repeatedly, I have probably not thought this through properly…
Yes, and you definitely need more coffee to think thoroughly!
I found myself often adding more fields to an existing Struct or adding parametric types as I proceed. At the very beginning, it may be hard to coin a perfect struct. I prefer starting coding as soon as I get the idea and polishing later as I go.
Another cause of restarts is when some function becomes unresponsive for a long time and ignores ctrl+c. This happens to me often.
Pluto is great for this: change functions/structs/whatever, it’s all automatically kept up-to-date.
When the code has somewhat stabilized, put it into a proper module/package outsides of the notebook. Struct changes tend to be much more rare afterwards.
Just develop in a module and reload the module.
Really wish that when people suggested this, they would be more specific about the way and the limitations. Modules, types, and macros aren’t as dynamic as a function’s method table is now, and though Revise.jl gets you farther, you need to do more work and can only go so far. I’ve seen a fair share of posts being confused why they “reloaded” a module but all the dependent modules still use the old version.
There really should be a citable central source of how different things reference each other in Julia, e.g. how does f
know what MyType
is in f(x::MyType) = _f(x)
, with the express goal of teaching people to reason what is and isn’t affected when code is reevaluated. Obviously it should state the caveat that some information are only implementation details.
I agree - I learned by experimenting. maybe there is docs maybe not - but there should be.
What do you mean by “develop in a module” and “reload the module”?
When you reload or re-evaluate the module the type can change. This is good enough for me most of the time but as Benny pointed out it doesn’t solve all problems
I mean how do you do that? Sorry for the stupid question.
Do your bleeding-edge development in a project, where you have a file like
module WIP
struct DraftType
# etc
end
function foo(x::DraftType); do_stuff(x); end
# maybe include("other_bits.jl")
end
Now from the REPL (i.e. the Main
module), do
include("wip.jl")
Experiment with WIP.DraftType
and WIP.foo
. When you find mistakes or deficiencies, edit and include
over again. You need to remember to recreate any DraftType
objects. Do not do using WIP
in the REPL, to avoid being haunted by ghosts.
Wow, I do not know this before. Thanks! But I feel this does not fit my workflow well. I have a relatively large package to maintain and sometimes I need to update my struct definition. I have to experiment with several ways to update the struct to find which one is better.
You can take it to the extreme of
include(joinpath(ENV["HOME"] ,".julia/dev/MyPackage/src/MyPackage.jl"))
so your large package is an ephemeral module for the duration. I’ve found this to be productive, but it does take some extra thought and effort.
You can do this in other simple ways. Create a module structs
at the top and define all your structs inside of it. Experiment with the structs as you wish and modify structs inside the module. When you are finished, remove the module Structs
and its end
statements as well as all Structs.
prefixes.
module Structs
struct Point
x::Float64
y::Float64
end
end
Structs.Point(1.0,2.0)
# Main.Structs.Point(1.0, 2.0)
module Structs
struct Point
x::Float64
y::Float64
z::Float64
end
end
Structs.Point(1.0,2.0,3.0)
# Main.Structs.Point(1.0, 2.0, 3.0)
In this workflow, one would also need to redefine functions that accept WIP.DraftType
:
julia> module WIP
struct DraftType
# etc
end
function foo(x::DraftType); do_stuff(x); end
# maybe include("other_bits.jl")
end
Main.WIP
julia> bar(::WIP.DraftType) = 2
bar (generic function with 1 method)
julia> bar(WIP.DraftType())
2
julia> module WIP
struct DraftType
# etc
end
function foo(x::DraftType); do_stuff(x); end
# maybe include("other_bits.jl")
end
WARNING: replacing module WIP.
Main.WIP
julia> bar(WIP.DraftType())
ERROR: MethodError: no method matching bar(::Main.WIP.DraftType)
Closest candidates are:
bar(::Main.WIP.DraftType) at REPL[2]:1
Stacktrace:
[1] top-level scope
@ REPL[4]:1
julia> methods(bar)
# 1 method for generic function "bar":
[1] bar(::Main.WIP.DraftType) in Main at REPL[2]:1
julia> bar(::WIP.DraftType) = 2
bar (generic function with 2 methods)
julia> bar(WIP.DraftType())
2
I must say that the output of methods
is confusing, and wish that there was a way to indicate ghosting.
I‘ve never used it myself, but Stefan Karpinski said somewhere that using NamedTuples as structs during development is also possible and then swap them out by structs once you‘re done
I haven’t tried this but I can totally see how this would work a lot of the time.