Literally, yes. It's arguably much more readily apparent that code is just data when programming in assembly than in any other environment. It's all bytes, and you're forced to accept that from day 1.
You aren't going to have the expressiveness that a Lisp provides available to you, but self-modifying code and macros assemblers are common in some assembly programming scenes. Anything to save a few bytes or cycles. I'm a little rusty, but here's a basic example off the top of my head (sorry, wall of text incoming):
Functional programmers are familiar with the concept of "map," an operation that applies a function to every element in a list or what have you. Let's say I'm programming in 6502 assembly, and I have a little function that adds an amount to every byte in a page (or every byte in some particular 256 bytes). Let's say you also want a similar function that instead multiplies each byte by two (just a single left shift), or masks some bits off with a bitwise AND, or whatever. It would look something like this:
store zero in X register, add constant to the memory at the address (some constant address + value in X register), increment X, branch back to the adding part if the zero flag is not set (ie, loop until X overflows to 0), then return to wherever we called this function from.
You could write a handful of these almost identical subroutines, with the only real difference being the single opcode that reads, modifies, and rewrites each byte... or you could just rewrite the opcode at runtime!
Now, your add-to-page, multiply-page, mask-out-page, and any similar functions all share a little block of code that you can think of as "their map," and the actual functions you call initially could look something like this:
Write the constant for the relevant opcode to the instruction in map you want to replace ($7D for ADC absolute,X on the 6502), and jump to the map subroutine.
That's a bit of a contrived example, but in a routine with a more complicated access pattern, it might really make a difference in byte savings: Imagine a routine that clips offscreen entities in a game, that calculates something like "if their X coordinate is below or above some value, they disappear for now. If their Y coordinate is below some value, they fell into a pit and died." You could reuse a lot of the general logic for checking the left edge of the screen for the other two edges, just by rewriting a constant and instruction or two.
A perhaps more readily useful example is rewriting "constant" addresses. How would we rewrite our above "map" routine to modify arbitrary pages, and not just one in particular? We could store the address of the page we want to modify in memory, and use an indirect addressing mode for add. Indirect addressing modes of instructions do something like this: Load two bytes from some address in memory, and treat that as the address we want to operate on. Problem is, this indirect address mode adds two cycles to every operation we perform when compared to the original constant-address map. Suddenly, we're wasting over 500 cycles per map!
The solution is instead to treat the "constant" address in the instruction stream of the map routine as your address variable. Just rewrite those two bytes during your "function prologue," and voila, you have a general-purpose map routine that only uses half-a-dozen cycles or so more than one that only worked on a certain page.
The part I've been leaving out is using assembler macros to automate a lot of this stuff for you. Again, I'm rusty, and I never got all that experienced in writing macros, but someone could very easily write themself a macro (if they're using a powerful macro assembler, like ca65) that takes a single argument, the instruction you want to execute in your map, and generates for that stub subroutine that replaces the opcode for them. I found simpler macros to be more useful in everyday code, though: you make a macro that fills in a gap in the 6502's instruction set, like performing arithmetic between the accumulator and index registers, or basic 16-bit arithmetic, and from then on, you can pretend that the CPU had those instructions all along.
I may have only used these techniques for shaving bytes and cycles off of straightforward routines, but in some ways, going from writing 6502 assembler to writing C, Lua, and JavaScript actually feels like a step back in terms of expressiveness, even if they are certainly more productive languages in actuality, and first-class functions/function pointers cover much of the most practical (and least dangerous) use-cases for self-modifying code. I suppose I won't get that feeling back until I set some time aside to really learn a Lisp.
You aren't going to have the expressiveness that a Lisp provides available to you, but self-modifying code and macros assemblers are common in some assembly programming scenes. Anything to save a few bytes or cycles. I'm a little rusty, but here's a basic example off the top of my head (sorry, wall of text incoming):
Functional programmers are familiar with the concept of "map," an operation that applies a function to every element in a list or what have you. Let's say I'm programming in 6502 assembly, and I have a little function that adds an amount to every byte in a page (or every byte in some particular 256 bytes). Let's say you also want a similar function that instead multiplies each byte by two (just a single left shift), or masks some bits off with a bitwise AND, or whatever. It would look something like this:
store zero in X register, add constant to the memory at the address (some constant address + value in X register), increment X, branch back to the adding part if the zero flag is not set (ie, loop until X overflows to 0), then return to wherever we called this function from.
You could write a handful of these almost identical subroutines, with the only real difference being the single opcode that reads, modifies, and rewrites each byte... or you could just rewrite the opcode at runtime!
Now, your add-to-page, multiply-page, mask-out-page, and any similar functions all share a little block of code that you can think of as "their map," and the actual functions you call initially could look something like this:
Write the constant for the relevant opcode to the instruction in map you want to replace ($7D for ADC absolute,X on the 6502), and jump to the map subroutine.
That's a bit of a contrived example, but in a routine with a more complicated access pattern, it might really make a difference in byte savings: Imagine a routine that clips offscreen entities in a game, that calculates something like "if their X coordinate is below or above some value, they disappear for now. If their Y coordinate is below some value, they fell into a pit and died." You could reuse a lot of the general logic for checking the left edge of the screen for the other two edges, just by rewriting a constant and instruction or two.
A perhaps more readily useful example is rewriting "constant" addresses. How would we rewrite our above "map" routine to modify arbitrary pages, and not just one in particular? We could store the address of the page we want to modify in memory, and use an indirect addressing mode for add. Indirect addressing modes of instructions do something like this: Load two bytes from some address in memory, and treat that as the address we want to operate on. Problem is, this indirect address mode adds two cycles to every operation we perform when compared to the original constant-address map. Suddenly, we're wasting over 500 cycles per map!
The solution is instead to treat the "constant" address in the instruction stream of the map routine as your address variable. Just rewrite those two bytes during your "function prologue," and voila, you have a general-purpose map routine that only uses half-a-dozen cycles or so more than one that only worked on a certain page.
The part I've been leaving out is using assembler macros to automate a lot of this stuff for you. Again, I'm rusty, and I never got all that experienced in writing macros, but someone could very easily write themself a macro (if they're using a powerful macro assembler, like ca65) that takes a single argument, the instruction you want to execute in your map, and generates for that stub subroutine that replaces the opcode for them. I found simpler macros to be more useful in everyday code, though: you make a macro that fills in a gap in the 6502's instruction set, like performing arithmetic between the accumulator and index registers, or basic 16-bit arithmetic, and from then on, you can pretend that the CPU had those instructions all along.
I may have only used these techniques for shaving bytes and cycles off of straightforward routines, but in some ways, going from writing 6502 assembler to writing C, Lua, and JavaScript actually feels like a step back in terms of expressiveness, even if they are certainly more productive languages in actuality, and first-class functions/function pointers cover much of the most practical (and least dangerous) use-cases for self-modifying code. I suppose I won't get that feeling back until I set some time aside to really learn a Lisp.