Search code examples
eiffel

Eiffel: passing argument to agent received as argument


I'm really stuck in how to pass an argument to a second level of an Eiffel agent call. Hope my example is enough self explaining:

main_call
    do
        b (agent d(?, ?))
    end

b (a_proc: PROCEDURE[STRING])
    local
        l_something_else: INTEGER
    do
        l_something_else := 1
--        z("a_line", a_proc(l_something_else)) -- How can I pass at this stade l_something_else to the d procedure!!!
    end

d (line: STRING; something_else: INTEGER)
    do
--        do_some_stuff_with_line (line, something_else)
        do_nothing
    end

z (a_line: STRING; a_proc: PROCEDURE[STRING])
    do
        a_proc.call([a_line])
    end

I would have liked to be able to do something like

z("a_line", a_proc(?, something_else))

But its not possible, as I try with the agent keyword, the a_proc argument is not recognized!

So what would be the syntax? I even tried to add an argument to a_proc with a_proc.set_operands but am lost with the OPEN_ARGS class

Implementation case

If you need a goal... just imagine I'd like to have 2 different implementations of the d function, and in my case its using UT_CSV_HANDLER that I'd like to have 2 different functions for each line of a CVS

The following code gives a Non-compatible actual argument in feature call

Feature: import_from_csv_impl
Called feature: import_from_csv_impl (a_rest_request: REST_REQUEST; a_procedure: PROCEDURE [DS_ARRAYED_LIST [STRING_8], INTEGER_64, STRING_8]): [detachable like items] detachable SIT_LINKED_LIST [MEASURING_POINT] from MEASURING_POI...
Argument name: ia_name
Argument position: 2
Formal argument type: STRING_8
Actual argument type: INTEGER_64
Line: 258
                ia_procedure.call (ia_measuring_point_id, ia_name)
->            end (?, l_measuring_point_id, l_s)
            l_csv_handler.read_file (l_is, l_partially_closed)

The complete example:

-- Main call
import_from_abb_csv (a_rest_request: REST_REQUEST): detachable like items 
    do
        Result := import_from_csv_impl (a_rest_request, agent impl_for_each_csv_line_import_from_abb_csv)
    end

-- Second call
import_from_csv_impl (a_rest_request: REST_REQUEST; a_procedure: PROCEDURE[DS_ARRAYED_LIST [STRING_8], INTEGER_64, STRING]): detachable like items
    local
        l_csv_handler: UT_CSV_HANDLER
        l_is: KL_STRING_INPUT_STREAM
        l_measuring_point_id: INTEGER_64
        l_s: STRING
        l_partially_closed: PROCEDURE[DS_ARRAYED_LIST[STRING]]
    do
        l_s := "whatever"
        l_measuring_point_id := 12
        create l_csv_handler.make_with_separator (',')

        l_partially_closed := agent (i_al: DS_ARRAYED_LIST[STRING]; ia_measuring_point_id: INTEGER_64; ia_name: STRING; ia_procedure: PROCEDURE[INTEGER_64, STRING])
            do
                ia_procedure.call (ia_measuring_point_id, ia_name)
            end (?, l_measuring_point_id, l_s)

        l_csv_handler.read_file (l_is, l_partially_closed)

    end

-- end call
impl_for_each_csv_line_import_from_abb_csv (a_csv_line: DS_ARRAYED_LIST [STRING_8]; a_measuring_point_id: INTEGER_64; l_cu_name: STRING)
    do
        -- do_my_business
    end


-- for information signature of read_file is:
--     read_file (a_file: KI_TEXT_INPUT_STREAM; a_action: PROCEDURE [DS_ARRAYED_LIST [STRING]])

Solution

  • agent d or agent d (?,?) (they are both equivalent) produces a PROCEDURE [STRING, INTEGER], with both operands of d still open. Because of tuple covariance, PROCEDURE [STRING, INTEGER] conforms to PROCEDURE [STRING], thus your implementation of a compiles, but attempting to call the agent with only a TUPLE [STRING] instead of a TUPLE [STRING, INTEGER] as operands (which b does) will cause a runtime exception (probably a catcall).

    One way to progressively close the operands of an agent is to wrap it in another agent with one less open operand:

    b (a_procedure: PROCEDURE [STRING, INTEGER])
    local
        l_something_else: INTEGER
        l_partially_closed: PROCEDURE [STRING]
    do
        l_something_else := 1
    
        l_partially_closed := agent (ia_operand_1: STRING; ia_operand_2: INTEGER; ia_procedure: PROCEDURE [STRING, INTEGER])
        do
            ia_procedure.call (ia_operand_1, ia_operand_2)
        end (?, l_something_else, a_procedure)
            -- Notice how only one operand is left open
    
        z ("a_line", l_partially_closed)
    end
    

    Alternatively, l_something_else could be declared inside the inline agent:

    b (a_procedure: PROCEDURE [STRING, INTEGER])
    local
        l_partially_closed: PROCEDURE [STRING]
    do
        l_partially_closed := agent (ia_operand_1: STRING; ia_procedure: PROCEDURE [STRING, INTEGER])
        local
            il_something_else: INTEGER
        do
            il_something_else := 1
            ia_procedure.call (ia_operand_1, il_something_else)
        end (?, a_procedure)
    
        z ("a_line", l_partially_closed)
    end
    

    You could also use {ROUTINE}.set_operands and {ROUTINE}.apply, but it is less flexible and more error-prone in my opinion, as well as not thread-safe.

    b (a_procedure: PROCEDURE [STRING, INTEGER])
    local
        l_something_else: INTEGER
        l_partially_closed: PROCEDURE [STRING]
    do
        l_something_else := 1
        a_procedure.set_operands ("some string you will override later", l_something_else)
        z ("a_line", a_procedure)
    end
    
    z (a_line: STRING; a_proc: PROCEDURE [STRING]) -- or PROCEDURE [STRING, INTEGER]
    do
        -- You have no guarantee that `a_proc' has any operands set (though you could make it a precondition)
        -- This is why it is less safe and less reusable than the previous approach
        check attached a_proc.operands as la_operands then
            la_operands [1] = a_line
        end
        a_proc.apply
    end
    
    

    UPDATE

    Given your implementation case, see the comments starting with 'HERE':

    -- Main call
    import_from_abb_csv (a_rest_request: REST_REQUEST): detachable like items
        do
            Result := import_from_csv_impl (a_rest_request, agent impl_for_each_csv_line_import_from_abb_csv)
        end
    
    -- Second call
    import_from_csv_impl (a_rest_request: REST_REQUEST;
    a_procedure: PROCEDURE[DS_ARRAYED_LIST [STRING_8], INTEGER_64, STRING]): detachable like items
        local
            l_csv_handler: UT_CSV_HANDLER
            l_is: KL_STRING_INPUT_STREAM
            l_measuring_point_id: INTEGER_64
            l_s: STRING
            l_partially_closed: PROCEDURE[DS_ARRAYED_LIST[STRING]]
        do
            l_s := "whatever"
            l_measuring_point_id := 12
            create l_csv_handler.make_with_separator (',')
    
            l_partially_closed := agent (i_al: DS_ARRAYED_LIST[STRING];
                                        ia_measuring_point_id: INTEGER_64;
                                        ia_name: STRING;
                                        ia_procedure: PROCEDURE[DS_ARRAYED_LIST [STRING_8], INTEGER_64, STRING]) -- HERE, change the declared type of `ia_procedure' to match `a_procedure'
                do
                    ia_procedure.call (i_al, ia_measuring_point_id, ia_name) -- HERE, add `i_al' to `call'
                end (?, l_measuring_point_id, l_s, a_procedure) -- HERE, add `a_procedure'
    
            l_csv_handler.read_file (l_is, l_partially_closed)
    
        end
    
    -- end call
    
    impl_for_each_csv_line_import_from_abb_csv (a_csv_line: DS_ARRAYED_LIST [STRING_8]; a_measuring_point_id: INTEGER_64; l_cu_name: STRING)
        do
            -- do_my_business
        end
    
    
    -- for information signature of read_file is:
    --     read_file (a_file: KI_TEXT_INPUT_STREAM; a_action: PROCEDURE [DS_ARRAYED_LIST [STRING]])