# \[ANN\] StrucRev - revise type definitions as you go

**URL:** <https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959>\
**Category:** Package Announcements\
**Tags:** macros, revise, struct\
**Created:** [February 8, 2024, 10:52pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959 "2024-02-08T22:52:58Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [February 8, 2024, 10:52pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/1 "2024-02-08T22:52:58Z")

</div>

The package is intended to enable redefining structs within Revise.jl based workflow. It exports the macro `@strev` which (temporarily) wraps your struct like this

```julia
@strev struct S0
    x
end

```

becomes

```julia
module StrucRev_S0
  struct S0
      x
  end
end
S0 = StrucRev_S0.S0

```

If you edit `S0`, you will get a warning like  
`WARNING: replacing module StrucRev_S0`  
the S0 variable in your module will be re-bound to this new `StrucRev_S0`, and you can continue to code.

In case you want to define outer constructor methods, the usage is like following

```julia
@strev begin
    struct S0
        x::Int
    end
    S0(x::Real) = S0(Int(round(x))
end

```

If your struct definition contains references to some external variables, they will be imported into the struct-wrapping module.

Another usage is

```julia
@strev const c = 1

```

which will rewrite it to

```julia
c::typeof(1) = 1

```

thus enabling you to experiment with the value of `c` (but not with its type).

After you are finalized your types & constants, you simply remove all occurences of `@strev` (and, well, re-start `Julia`).

For more usage examples see [runtests.jl](https://github.com/Eben60/StrucRev.jl/blob/main/test/runtests.jl)

The package is not registered yet, and can be installed from [GitHub](https://github.com/Eben60/StrucRev.jl/). I haven’t yer done any really thorough test with it.

I still have some “imposter syndrome doubts” 😟 - first, if it is that simple, why nobody implemented it yet (or maybe it is already done), and, second, I am definitely not good at macroprogramming and maybe very naive and made some terrible mistakes. If that is not the case and the package is of some value, I plan to add some documentation, more testing, CI, and then register it.

Thanks are due to @disberd for advice and a piece of code.

P.S. Sure there exist packages for the same purpose, which somehow escaped my attention till now - see [discussion here](https://discourse.julialang.org/t/thoughts-and-tempering-expectations-on-redefining-structs/101190)

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [February 9, 2024, 1:47am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/2 "2024-02-09T01:47:43Z")

</div>

> [@Eben60](#):
>
> why nobody implemented it yet (or maybe it is already done)

I’m probably getting something wrong but the module wrapping seems like how Pluto notebooks are implemented, where you can redefine structs. Of course this more directly wraps the struct instead of a cell, and can be used in non-Pluto notebooks.

---

<div class="post-metadata">

**Author:** ![mrufsvold](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mrufsvold/32/31600_2.png) [@mrufsvold](https://discourse.julialang.org/u/mrufsvold)\
**Post date:** [February 9, 2024, 1:49am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/3 "2024-02-09T01:49:14Z")

</div>

@Benny is exactly right. This is how Pluto does it! It moves the entire notebook into a new module each time a struct changes and ports forward everything it can from the old module.

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 9, 2024, 1:56am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/4 "2024-02-09T01:56:57Z")

</div>

Very cool package!

I just want to suggest a name change if possible: `StructRev` instead of `StrucRev`

I’m also thinking about `@rev` instead of `@strev` but maybe this is more controversial 😃 at the same time it plays better with the revision of constant variables, I’m not sure if there is some other macro out there already called `@rev`

---

<div class="post-metadata">

**Author:** ![Alec\_Loudenback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/alec_loudenback/32/278_2.png) [@Alec\_Loudenback](https://discourse.julialang.org/u/Alec_Loudenback)\
**Post date:** [February 9, 2024, 4:30am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/5 "2024-02-09T04:30:58Z")

</div>

Or yet still: `StructRevise` may be more idiomatic for Julia packages.

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [February 9, 2024, 7:34am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/6 "2024-02-09T07:34:50Z")

</div>

I may have missed something, but if I understand correctly what `StructRev` does, I would anticipate the following issue:

```julia
# This is the equivalent for @strev Foo IIUC
julia> module Module_Foo
       struct Foo
           x::Int
       end
       end
Main.Module_Foo

julia> Foo = Module_Foo.Foo
Main.Module_Foo.Foo

# Now we define a method working on `Foo`
julia> foo(a::Foo) = a.x
foo (generic function with 1 method)

# And we use it on a newly created instance
julia> a = Foo(42)
Main.Module_Foo.Foo(42)

# So far so good
julia> foo(a)
42

```

Now what happens when we try to change the definition of `Foo`:

```julia
# Again, this is the expansion of @strev Foo IIUC
julia> module Module_Foo
       struct Foo
           x::Int
           y::Int
       end
       end
WARNING: replacing module Module_Foo.
Main.Module_Foo

julia> Foo = Module_Foo.Foo
Main.Module_Foo.Foo

# We have to create a new instance (the old one is outdated, fair enough)
julia> b = Foo(42, 43)
Main.Module_Foo.Foo(42, 43)

# But the problem is, methods that have been defined to accept the old
# struct definition don't know about the new one.
#
# (And arguably the error message is confusing!)
julia> foo(b)
ERROR: MethodError: no method matching foo(::Main.Module_Foo.Foo)

Closest candidates are:
  foo(::Main.Module_Foo.Foo)
   @ Main REPL[3]:1

```

Does `StrucRev` have a way to deal with this?

As far as I understand, tools like Pluto avoid this by putting _everything_ into a new module: the new struct definition, all method definitions and all uses of the structure.

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [February 9, 2024, 10:57am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/7 "2024-02-09T10:57:22Z")

</div>

> [@ffevotte](#):
>
> Does `StrucRev` have a way to deal with this?

Yes, you are right, that appears to be a problem.

It is currently possible to put all constructors into the new module, and it will be possible to implement putting everything else therein, but then it would be definitely much less elegant.

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [February 9, 2024, 12:13pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/8 "2024-02-09T12:13:49Z")

</div>

I once experimented with an idea along the lines of:

```julia
# file `mysrc.jl`

# This macro does nothing
# But when anything passed to the macro changes, `Revise` will have to
# revise everything. This is a (hacky?) way of forcing a bunch of
# definitions to be revised together.
macro revise_together(expr)
    esc(expr)
end

# We use it to group both the struct definition and the related methods
@revise_together begin
    struct Foo_v1
        x :: Int
    end
    Foo = Foo_v1

    foo(a::Foo) = a.x
end

```

Now, in a REPL:

```julia-repl
julia> includet("mysrc.jl")

julia> a = Foo(42)
Foo_v1(42)

julia> foo(a)
42

```

If you now change the struct definition in the file to (in the real thing, the versioning would be done by an other macro):

```julia
    struct Foo_v2
        x :: Int
        y :: Int
    end
    Foo = Foo_v2

```

you can go on in the same REPL:

```julia-repl
julia> b = Foo(42, 43)
Foo_v2(42, 43)

julia> foo(b)
42

julia> methods(foo)
# 1 method for generic function "foo" from Main:
 [1] foo(a::Foo_v2)
     @ /tmp/mysrc.jl:12

```

I actually can’t remember why I didn’t explore this further. Of course there is the issue that it’s not always practical to group all methods pertaining to a struct near the struct definition. But I’m kind of confusely remembering that there was a more fundamental reason why the effectivity of this technique was limited, but I just can’t remember exactly why.

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [February 10, 2024, 10:13pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/9 "2024-02-10T22:13:12Z")

</div>

Now compared my approach with the “competition” - the packages RedefStructs.jl and ProtoStructs.jl .

RedefStructs.jl appears to be broken under Julia 1.10 , at least it errored on a minimal `struct` definition.

ProtoStructs.jl worked, however it suffers ~~from the same problem as described above by~~ @ffevotte \*.

My package appears on the first glance to have all the abilities of ProtoStructs.jl (it supports `@kwdef` BTW), and provides the possibility to (re-)define inner and outer constructors, which appear to lack in the above package.

Thus it is indeed worth to register my package in its current form (which I was not sure). Will be done after adding some documentation.

The future plan is to enable co-wrapping of related methods. In the limiting case wrapping of “everything”.

@Tortar, @Alec_Loudenback - bikeshedding is welcome, and naming is difficult. For the macro name, I’d stay with the current one. For the package name, I’m not sure yet, but `StructRevise` could indeed be a better choice.

\*EDIT: It is a different problem, see below.

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 10, 2024, 10:31pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/10 "2024-02-10T22:31:31Z")

</div>

While I think your approach is worthwhile it is not true that ProtoStructs.jl suffers from the same problems:

```julia
julia> using ProtoStructs

julia> @proto struct Foo
                  x::Int
              end

julia> foo(a::Foo) = a.x
foo (generic function with 1 method)

julia> a = Foo(42)
Foo(42)

julia> foo(a)
42

julia> @proto struct Foo
                  x::Int
                  y::Int
              end

julia> b = Foo(42, 43)
Foo(42, 43)

julia> foo(b)
42

```

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [February 10, 2024, 10:59pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/11 "2024-02-10T22:59:00Z")

</div>

It could be a different problem, but with an “`includet`” file I got an error

```julia
Error: Failed to revise ...
│ exception =
│ AssertionError: ld[idx] < typemax(eltype(ld))

```

Right now it’s midnight here, tomorrow I’d provide more details.

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 10, 2024, 11:53pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/12 "2024-02-10T23:53:31Z")

</div>

yes workflows using Revise don’t play well at the moment with ProtoStructs, but this is not a fundamental limitation as far as I can tell, more than nobody has made a PR fixing it yet

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [February 11, 2024, 10:33am UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/13 "2024-02-11T10:33:52Z")

</div>

[Reported](https://github.com/BeastyBlacksmith/ProtoStructs.jl/issues/13) the issue on the GitHub

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 11, 2024, 5:02pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/14 "2024-02-11T17:02:38Z")

</div>

made a PR trying to fix the compatibilty problem: [Create revisableproto macro for compatibility with Revise by Tortar · Pull Request #35 · BeastyBlacksmith/ProtoStructs.jl · GitHub](https://github.com/BeastyBlacksmith/ProtoStructs.jl/pull/35) if someone is interested/would like to propose a better solution

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [February 18, 2024, 12:48pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/15 "2024-02-18T12:48:37Z")

</div>

Is it somehow possible do detect whether the code runs under `Revise` or outside of it? There should be a way, I think.

BTW I’ve done a short test of your PR in a “Package under `Revise`” - both re-definition of structs and the of dependent methods appear to function OK.

---

<div class="post-metadata">

**Author:** ![Tortar](https://avatars.discourse-cdn.com/v4/letter/t/6bbea6/32.png) [@Tortar](https://discourse.julialang.org/u/Tortar)\
**Post date:** [February 18, 2024, 6:00pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/16 "2024-02-18T18:00:06Z")

</div>

yes, it could be maybe possible, I asked a question about it [Is there a way to know if `Revise` is enabled?](https://discourse.julialang.org/t/is-there-a-way-to-know-if-revise-is-enabled/110379)

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [March 1, 2024, 12:51pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/17 "2024-03-01T12:51:34Z")

</div>

Now that this thread got attention from “This month in Julia world” - the current state:

- ProtoStructs.jl works now under `Revise`, too. The only substantial limitation appears to be lack of support for inner constructors.
- No further progress for `StrucRev` yet due to lack of time, with some thought about the path to solve the problems to be discussed later.

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [July 28, 2024, 3:06pm UTC](https://discourse.julialang.org/t/ann-strucrev-revise-type-definitions-as-you-go/109959/18 "2024-07-28T15:06:50Z")

</div>

In the meanwhile there may be a chance for a general solution [in sight](https://discourse.julialang.org/t/this-month-in-julia-world-2024-05/115278/1)

> [@This month in Julia world - 2024-05](https://discourse.julialang.org/t/this-month-in-julia-world-2024-05/115278/1):
>
> This might be changing soon (enabling Revise to redefine structs). See this [old proposal](https://github.com/JuliaLang/julia/issues/40399) and [recent (well documented) work on implementation](https://github.com/JuliaLang/julia/pull/54654).

and most probably the problem is above my competence anyway. Still, the following idea, rather vague one:

```julia
module Module_Foo
    struct Foo
        x::Int
    end
end
Foo = Module_Foo.Foo

# now we want to re-define Foo
fm = methodswith(Foo)

sources = [m.source_ref for m in fm] # pseudocode

# delete "old" methods
for m in fm
    Base.delete_method(m)
end

# re-define Foo
module Module_Foo
    struct Foo
        x::Int
        y::Int
    end
end
Foo = Module_Foo.Foo 

for s in sources
    recompile(s) # pseudocode
end

```

in separate place:

```julia
foo(a::Foo) = a.x

```

Any idea what to put on the place of the pseudocode?
