# How to implement custom assignment with \`:=\` ? (for tinkering/learning purposes)

**URL:** <https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467>\
**Category:** Internals & Design\
**Tags:** question, lowering, assignment\
**Created:** [October 18, 2024, 7:28pm UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467 "2024-10-18T19:28:43Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [October 18, 2024, 7:28pm UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/1 "2024-10-18T19:28:43Z")

</div>

I stumbled across this [post](https://discourse.julialang.org/t/on-using-vs-for-assignment/110302) and realized I have no idea how I would actually implement `:=` to mean something like “define/assign the variable if it is not already defined, otherwise throw an error”, so I’m looking for some (any) pointers to get started.

This could also just be pointing me at the right files in the julia source or docs, or commenting on my current understanding of what’s going on under the hood so far (see below). Any hints are appreciated 🙂

> **What I think is going on so far**
>
> My first thoughts were: It should be possible to write this as a macro like so
> 
> ```julia
> @newsyntax a := 1
> 
> ```
> 
> Which could take `a := 1` and transform it into code that checks the definition of `a` and then does a normal assignment or throws the error. But that’s clunky of course!
> 
> My next thought was that I probably want to compare what the expressions `a = 1` and `a := 1` get lowered to and it seems like this is the stage that one would need to customize.
> 
> ```julia-repl
> julia> Meta.@lower a = 1
> :($(Expr(:thunk, CodeInfo(
> @ none within `top-level scope`
> 1 ─ %1 = Core.get_binding_type(Main, :a)
> │ #s1 = 1
> │ %3 = #s1 isa %1
> └── goto #3 if not %3
> 2 ─ goto #4
> 3 ─ #s1 = Base.convert(%1, #s1)
> 4 ┄ a = #s1
> └── return 1
> ))))
> 
> ```
> 
> ```julia-repl
> julia> Meta.@lower a := 1
> :($(Expr(:error, "unsupported assignment operator \":=\"")))
> 
> ```
> 
> Aha! So somewhere between the expression and the lowered IR Julia figures out that `:=` is not valid syntax (yet!).
> 
> Since `Meta.lower` seems to just do `ccall(:jl_expand, ...)` the interesting part happens in a C funtion, which I could customize – is this correct so far?
> 
> So the next steps would be
> 
> - find the function `jl_expand` in the Julia source and edit it accordingly
> - compile Julia from the changed source files
> 
> Or is there a more straightforward way that allows this kind of rewriting from within Julia?

Disclaimer:  
I don’t care if this becomes Julia syntax or not and the precise meaning of `:=` is also not important for me – I just want to learn how to figure out and change the current behavior of Julia internals.

---

<div class="post-metadata">

**Author:** ![Palli](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/palli/32/3380_2.png) [@Palli](https://discourse.julialang.org/u/Palli)\
**Post date:** [October 18, 2024, 7:33pm UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/2 "2024-10-18T19:33:54Z")

</div>

The parser has changed, and something like here to look at (assuming you don’t want to change the deprecated flisp parser too):

> <https://github.com/JuliaLang/JuliaSyntax.jl/blob/9483de85a3e59f017330f6f36a083e7951262e87/src/tokenize.jl#L497-L498>

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [October 18, 2024, 7:41pm UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/3 "2024-10-18T19:41:15Z")

</div>

Oh I see, thanks for the pointer!

Making changes in JuliaSyntax.jl would definitely have less overhead than digging into the previous parser).

---

<div class="post-metadata">

**Author:** ![savq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/savq/32/22063_2.png) [@savq](https://discourse.julialang.org/u/savq)\
**Post date:** [October 20, 2024, 4:38am UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/4 "2024-10-20T04:38:27Z")

</div>

> [@Sevi](#):
>
> My first thoughts were: It should be possible to write this as a macro like so
> 
> ```julia
> @newsyntax a := 1
> 
> ```
> 
> Which could take `a := 1` and transform it into code that checks the definition of `a` and then does a normal assignment or throws the error. But that’s clunky of course!

It’d be clunky if you had to invoke it for every `:=`, but if you wrote a function that walked entire expression trees, then it becomes much easier. You could then pass that function to `include` so you’d only need to import that function to a package top level.

Extremely barebones MWE:

In `foo.jl`:

```julia
x := 1
y = 2
y := 3

println(x)
println(y)

```

In repl:

```julia-repl
julia> function colon_def(expr)
           if expr.head === :(:=)
               expr.head = :(=) # or whatever
               return expr
           else
               return expr
           end
       end
colon_def (generic function with 1 method)

julia> include(colon_def, "./foo.jl")
1
3

```

---

<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:** [October 20, 2024, 4:42am UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/5 "2024-10-20T04:42:28Z")

</div>

`include_string` can work on inlined text, and that could be done in a string literal, a syntax often used for evaluating code in other languages’ interpreters.

```julia
julia> macro tweakedparse_str(code::String)
         :(include_string(colon_def, @ __MODULE__ , $code)) # iffy on this getting to the right module
       end
@tweakedparse_str (macro with 1 method)

julia> tweakedparse"""
       x := 1
       y = 2
       y := 3

       println(x)
       println(y)
       """
1
3

```

Note that non-standard string literals are not parsed with `$`-interpolation and `\`-unescaping like `String` literals are. `@raw_str` is a macro that actually does nothing to the input `String`, so non-standard string literal inputs can be thought of as raw strings. I’m not aware of a way to opt into the `String` literal parsing behavior inside a macro, so if you need it, the wordier `include_string` call can be used directly.

I think a macro should work for inline code too, just needs a manual way to work on each subexpression of a `begin` block:

```julia
@tweakedparse2 begin
#= lines of code =#
end

```

---

<div class="post-metadata">

**Author:** ![eldee](https://avatars.discourse-cdn.com/v4/letter/e/b5a626/32.png) [@eldee](https://discourse.julialang.org/u/eldee)\
**Post date:** [October 20, 2024, 8:49am UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/6 "2024-10-20T08:49:56Z")

</div>

> [@Benny](#):
>
> I think a macro should work for inline code too, just needs a manual way to work on each subexpression of a `begin` block:

Sounds like a job for `MacroTools.jl`’s `@capture` and `postwalk`:

```julia-repl
julia> using MacroTools: @capture, postwalk

julia> macro tweakedparse2(ex)
           return postwalk(ex) do x
               if @capture(x, a_ := b_)
                  return quote 
                       if @isdefined($a) 
                           error("Variable " * $(sprint(show_unquoted, a)) * " is already defined")
                       end                       
                       $a = $b
                   end 
               else 
                   return x
               end
           end
       end
@tweakedparse2 (macro with 1 method)

julia> @tweakedparse2 begin
           x := 1
           y = 2
           z := 3

           @show x y z x + y + z
       end
x = 1
y = 2
z = 3
x + y + z = 6
6

julia> @tweakedparse2 begin
           x := 1 # Fine
           x = 2 # Fine
           x := 3 # Error
       end
ERROR: Variable x is already defined
Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] top-level scope
   @ REPL[4]:4

```

Edit: added variable name to error message (cf. `@show` macro implementation) and split `tweakedparse2` macro into multiple lines for easier readability.

---

<div class="post-metadata">

**Author:** ![Sevi](https://avatars.discourse-cdn.com/v4/letter/s/c67d28/32.png) [@Sevi](https://discourse.julialang.org/u/Sevi)\
**Post date:** [October 21, 2024, 7:37am UTC](https://discourse.julialang.org/t/how-to-implement-custom-assignment-with-for-tinkering-learning-purposes/121467/7 "2024-10-21T07:37:35Z")

</div>

> [@savq](#):
>
> ```julia-repl
> include(colon_def, "./foo.jl")
> 
> ```

> [@Benny](#):
>
> ```julia
> include_string(colon_def, @ __MODULE__ , $code)
> 
> ```

Thanks for these suggestions! That’s really convenient, I wasn’t aware that one can transform every expression in the included file (or string) in this way.
