dmcoles / EVO

E-VO Amiga E Compiler
45 stars 5 forks source link

Why does this work? #11

Closed mdbergmann closed 4 months ago

mdbergmann commented 4 months ago

Sorry to spam the ticket reporting system. I don't really know where to discuss AmigaE coding. So please let me know where is a better place (anyone reading here, maybe open Discussions?).

I've again played a bit with AmigaE lately and thought I'd add some higher-level functions/PROCs, i.e. for 'reduce/fold'.

What I came up with is this:

EXPORT PROC reduce(lst, initialValue, fun)
    DEF accu, it, tmp, len, i
    len := ListLen(lst)
    accu := initialValue
    FOR i := 0 TO len-1
        it := ListItem(lst, i)
        tmp := accu
        accu := Eval(fun)
    ENDFOR
ENDPROC accu

Called like so using a quoted expression:

    DEF result, accu, it
    result := reduce([1,2,3,4,5], 0, `accu+it)
    PrintF('reduce result: \d\n', result)
    assertTrue(result = 15, 'Is not 15')

In the reduce function the tmp := accu is necessary, otherwise it won't work. But I do not understand why. Can someone shed some light?

I've also seen some built-in function like MapList use a variable pointer ({x}), but I'm not sure how this is used in the implementation and why is it needed. My variant works with convention that accu and it must be used.

dmcoles commented 4 months ago

These constructs were part of the original E compiler and I've only briefly played with them so I'm not sure how much insight I can provide. There is an AmigaE Facebook group https://www.facebook.com/share/2dSdayHNMFrRYKof/ and although it's not a particularly active or large group, Wouter van Oortmerssen who originally designed and wrote AmigaE is a member of that group so he might be able to assist. No idea if he will respond or not though

mdbergmann commented 4 months ago

Thanks

idrougge commented 4 months ago

https://cshandley.co.uk/amigae/ch_11c.html

mdbergmann commented 4 months ago

Yeah, thanks. I looked at this but my questions remain.

dmcoles commented 4 months ago

ok i had a look at this for you.

because you are using local variables it is by complete luck that this works really. if you make ACCU and IT global variables it works fine.

you are declaring accu and it as local variables here

DEF result, accu, it
result := reduce([1,2,3,4,5], 0, `accu+it)
PrintF('reduce result: \d\n', result)
assertTrue(result = 15, 'Is not 15')

the code to calculate accu+it is compiled using references to the local variables declared here and then you pass a reference to that code into the reduce function it expects to be able to find the accu and it variables on the stack at a particular offset that was correct for the parent function.

by setting tmp you just hit the correct address by sheer luck. if you want do do what you are doing and pass the quoted code into a different function you really need to avoid using local variables within the quoted code as they wont be in the right place on the stack anymore. you might get away with it if you declared the exact same local variables in both routines but i wouldnt risk it.

this code works fine...


DEF accu, it

PROC reduce(lst, initialValue, fun)
    DEF tmp, len, i
    len := ListLen(lst)
    accu := initialValue
    FOR i := 0 TO len-1
        it := ListItem(lst, i)
        accu := Eval(fun)
    ENDFOR
ENDPROC accu

PROC assertTrue(value,msg)
  IF Not(value)
    Throw(1,msg)
  ENDIF
ENDPROC

PROC main() HANDLE
  DEF result
  result := reduce([1,2,3,4,5], 0, `accu+it)

  PrintF('reduce result: \d\n', result)
  assertTrue(result = 15, 'Is not 15')
EXCEPT
  WriteF('fail \s\n',exceptioninfo)
ENDPROC
dmcoles commented 4 months ago

and to answer your question about the map list function. Maplist iterates over the provided list and performs an operation on each of those items but then returns a new list. You pass in the new list in the first parameter.

So in the example in the user manual

MapList({x},[1,2,3,4,5],r,x*x) results in: [1,4,9,16,25]`

x should be a variable created as a list to hold the results.

I'm not sure why it has to be passed in as {x} - i guess thats just a quirk of the way the language works.

mdbergmann commented 4 months ago

Cool, thank you for looking into this.

The problem with global vars is of course that i.e. the reduce function here should be a library function sitting in another module that doesn't know about the global variable.

In MapList I was actually thinking that r holds the result list (maybe I'm wrong) and {x} somehow maybe works around the problem with the global/local variables that you were talking about?

dmcoles commented 4 months ago

this works because it pulls in the globals from the module

reduce.e

OPT MODULE

EXPORT DEF accu,it

EXPORT PROC reduce(lst, initialValue, fun)
    DEF tmp, len, i
    len := ListLen(lst)
    accu := initialValue
    FOR i := 0 TO len-1
        it := ListItem(lst, i)
        accu := Eval(fun)
    ENDFOR
ENDPROC accu

main.e

MODULE '*reduce'

PROC assertTrue(value,msg)
  IF Not(value)
    Throw(1,msg)
  ENDIF
ENDPROC

PROC main() HANDLE
  DEF result
  result := reduce([1,2,3,4,5], 0, `accu+it)

  PrintF('reduce result: \d\n', result)
  assertTrue(result = 15, 'Is not 15')
EXCEPT
  WriteF('fail \s\n',exceptioninfo)
ENDPROC
mdbergmann commented 4 months ago

OK, that's a good idea. I'm used to multi-threading, in which case this would of course be a problem because not thread-safe. But on Amiga it's no problem, or?

dmcoles commented 4 months ago

Yes that is a valid point. If you tried to use it from two threads at the same time it could cause issues

It's not an ideal solution. It's a bit strange passing in a piece of code that references variables they aren't obvious what they do since they are hidden away in the module code

I wonder if there's a better way, I'll have a think on it the built in functions don't have the same issues because they are not a separate procedure call so they don't have a problem the local variables.

mdbergmann commented 4 months ago

I'm a little torn between the simplicity of a quoted expression and a function pointer as a 'lambda'. Quoted expressions, as they are right now, are simple but seem error prone, because the behavior is not fully clear. On the other hand function pointers do the right thing, but this requires to define a PROC.

like:

PROC myAddReduceFun(accu, it)
ENDPROC accu+it

reduce([1,2,3,4], 0, {myAddReduceFun})

In that regard it would be awesome being able to define anonymous functions as something like:

reduce([1,2,3,4], 0, &(accu, it => accu+it)   -> or some such syntax