• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

multiple return values

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
Suppose we wanted to write the function iminmax that took two integers A and B and would return to us
the minumum and the maximum of the two, well... we "can't" do it in jass because we can't return multiple values.

What do other langauges that can't return multiple values do? Well they return records/structs, but hey vJass has structs could we use those? Maybe, lets see:

JASS:
struct iminmax_result
    int min
    int max
endstruct

function iminmax takes integer a, integer b returns iminmax_result
    local iminmax_result imm = iminmax_result.create()

    if a < b then
        set imm.min = a
        set imm.max = b
    else
        set imm.min = b
        set imm.max = a
    endif

    return imm
endfunction

function use_iminmax takes nothing returns nothing
    local iminmax_result imm = iminmax(GetHeroStr(my_hero, /*include-bonuses*/ true), GetHeroAgi(my_hero, /*include-bonuses*/ true))
    // do something, idk, the higher stat deals damage, the lower heals the hero or something... doesn't matter
endfunction

Notice that there's a slight problem now, if iminmax was called more than 8190 times it will fail because we would run out of struct instances,
one way to fix that would be to force the caller to call the imm.destroy method, and that would work, but it's somewhat error prone and annoying.

In order not to force the caller to call .destroy we can use our own struct allocation scheme and thankfully vJass allows us to do that (via "extends array"):

JASS:
struct iminmax_result extends array // "extends array" instructs the vJass preprocessor (jasshelper.exe) not to create a default allocate/create method because we will write our own
    integer min
    integer max

    static iminmax_result next_result = 0
    // we can call our "create" method whatever we think makes the most sense
    static method new_result takes nothing returns iminmax_result
        set next_result = next_result + 1
        if next_result >= 8191 then
            set next_result = 1
        endif
        return next_result
    endmethod
endstruct

function iminmax takes integer a, integer b returns iminmax_result
    local iminmax_result result = iminmax_result.new_result()

    if a < b then
        set result.min = a
        set result.max = b
    else
        set result.min = b
        set result.max = a
    endif

    return result
endfunction

function use_iminmax takes nothing returns nothing
    local iminmax_result imm = iminmax(GetHeroStr(my_hero, /*include-bonuses*/ true), GetHeroAgi(my_hero, /*include-bonuses*/ true))
    // do something, idk, the higher stat deals damage, the lower heals the hero, or something... doesn't matter
endfunction

Good, now users of iminmax, can call it as much as they want and don't have to worry about "destroying" things.

Now, let's say we want to write the function rminmax which works the same way as iminmax but for real numbers not integers, well we could just
copy the iminmax struct rename it and it's members types and then go and write rminmax.

But notice that the code for the new_result method and the next_result variable will always stay the same, no matter what the other members of the result struct were.
We can avoid having to write/copy-paste this duplication by hand by using the module feature of vJass:

JASS:
module Result_Struct
    static thistype next_result = 0

    static method new_result takes nothing returns thistype
        set next_result = next_result + 1
        if next_result >= 8191 then
            set next_result = 1
        endif
        return next_result
    endmethod
endmodule

And now the rminmax_result struct would look like:

JASS:
struct rminmax_result extends array // we, again, don't want vJass's default methods
    real min
    real max

    implement Result_Struct
endstruct

and the rminmax function

JASS:
function rminmax takes real a, real b returns rminmax_result
    local rminmax_result result = rminmax_result.new_result()

    if a < b then
        set result.min = a
        set result.max = b
    else
        set result.min = b
        set result.max = a
    endif

    return result
endfunction

And that's basically it, we can now easily write functions that have multiple return values.
 
When indices gets reused it's anyway not safe for the user to take long time usage of the returned struct,
so maybe there can be a constant static thistype which is user over and over again instead of switching around.

edit:
When we work with handles we though must think of an internal destroyer. So we should think of an approach with automated destroying included.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
You could use tuple types, which basically look like stucts but use no allocation (no overhead, no 8k instances limit, dont need to be destroyed):

JASS:
tuple iminmax_res(int min, int max)

function iminmax(int a, int b) returns iminmax_res
    if a < b
        return iminmax_res(a, b)
    else
        return iminmax_res(b,a)
 
Level 13
Joined
Nov 7, 2014
Messages
571
You could use tuple types, which basically look like stucts but use no allocation (no overhead, no 8k instances limit, dont need to be destroyed):

Well if the compiler has tuples, the yes those should be used for multiple return values instead (but that's not the case for vJass, I think).

About the overhead... I think it can be reduced down to 1 or 2 assignments and 1 array lookup instead of a function call:

JASS:
library resultstruct

private module array_new
    static /*readonly*/ integer array new

    private static method onInit takes nothing returns nothing
        local integer i = 1
        loop
            exitwhen i >= 8190
            set new[i] = i + 1 // 1 -> 2, 2 -> 3, ..., 8189 -> 8190
            set i = i + 1
        endloop
        set new[8190] = 1 // 8190 -> 1
    endmethod
endmodule

struct resultstruct extends array
    implement array_new
endstruct

module Result_Struct
    static thistype result = 0
endmodule

endlibrary

library libminmax uses resultstruct

struct iminmax_result extends array
    integer min
    integer max
    implement Result_Struct
endstruct

function iminmax takes integer a, integer b returns iminmax_result
    // an overhead of 2 assignments and 1 array lookup
    local iminmax_result result = resultstruct.new[iminmax_result.result]
    set iminmax_result.result = result

    if a < b then
        set result.min = a
        set result.max = b
    else
        set result.min = b
        set result.max = a
    endif

    return result
endfunction

struct rminmax_result extends array
    real min
    real max
    implement Result_Struct
endstruct

function rminmax takes real a, real b returns rminmax_result
    // an overhead of 1 assignment and 1 array lookup
    set rminmax_result.result = resultstruct.new[rminmax_result.result]

    // but that makes it somewhat harder to use

    if a < b then
        set rminmax_result.result.min = a
        set rminmax_result.result.max = b
    else
        set rminmax_result.result.min = b
        set rminmax_result.result.max = a
    endif

    return rminmax_result.result
endfunction

endlibrary


When indices gets reused it's anyway not safe for the user to take long time usage of the returned struct,
so maybe there can be a constant static thistype which is user over and over again instead of switching around.

I guess constant static thistype could be used but that would limit the number of times the iminmax function (for example)
could be called from a function to 1, because the second call would overwrite the values, and that's some "spooky action at a distance" right there,
or it requires the caller to be extra carefull and at this point you might as well ask him to .destroy.
 
Level 6
Joined
Jul 30, 2013
Messages
282
Well if the compiler has tuples, the yes those should be used for multiple return values instead (but that's not the case for vJass, I think).

About the overhead... I think it can be reduced down to 1 or 2 assignments and 1 array lookup instead of a function call:

JASS:
library resultstruct

private module array_new
    static /*readonly*/ integer array new

    private static method onInit takes nothing returns nothing
        local integer i = 1
        loop
            exitwhen i >= 8190
            set new[i] = i + 1 // 1 -> 2, 2 -> 3, ..., 8189 -> 8190
            set i = i + 1
        endloop
        set new[8190] = 1 // 8190 -> 1
    endmethod
endmodule

struct resultstruct extends array
    implement array_new
endstruct

module Result_Struct
    static thistype result = 0
endmodule

endlibrary

library libminmax uses resultstruct

struct iminmax_result extends array
    integer min
    integer max
    implement Result_Struct
endstruct

function iminmax takes integer a, integer b returns iminmax_result
    // an overhead of 2 assignments and 1 array lookup
    local iminmax_result result = resultstruct.new[iminmax_result.result]
    set iminmax_result.result = result

    if a < b then
        set result.min = a
        set result.max = b
    else
        set result.min = b
        set result.max = a
    endif

    return result
endfunction

struct rminmax_result extends array
    real min
    real max
    implement Result_Struct
endstruct

function rminmax takes real a, real b returns rminmax_result
    // an overhead of 1 assignment and 1 array lookup
    set rminmax_result.result = resultstruct.new[rminmax_result.result]

    // but that makes it somewhat harder to use

    if a < b then
        set rminmax_result.result.min = a
        set rminmax_result.result.max = b
    else
        set rminmax_result.result.min = b
        set rminmax_result.result.max = a
    endif

    return rminmax_result.result
endfunction

endlibrary




I guess constant static thistype could be used but that would limit the number of times the iminmax function (for example)
could be called from a function to 1, because the second call would overwrite the values, and that's some "spooky action at a distance" right there,
or it requires the caller to be extra carefull and at this point you might as well ask him to .destroy.

omg.. do you really need 2 classes just to return a value..
and that module.. i mean i get they are great but that doesn not mean you have to stick them every place u can imagine..

this is starting to remind me too much of JAVA already, make 3 classes for every trivial little task because you like classes so much..
but logically it just makes ur code unnecessarily convoluted and hard to maintain, as well as bloats the source..


also this.. is just an abomonation:
JASS:
    local iminmax_result result = resultstruct.new[iminmax_result.result]
not onyl are you violating type safety (tho vjass allows it for now, its not a feature but a lack of type validation on user defined types..)
(also: that array lookup thing looks just like a micro optimisation trick, you do know vjass inlines trivial functions right?! this looks a LOT like premature optimisation at the cost of readability no less)

and behold some completely useless indirection that buys us nothing.
JASS:
struct resultstruct extends array
    implement array_new
endstruct

module Result_Struct
    static thistype result = 0
endmodule

...

set iminmax_result.result = result

i honestly cant see what you get with all this that you cant get with a simple struct with 2 members and a custom constructor (for the 8191 instance limit issue)
 
Status
Not open for further replies.
Top