Get a list of filtered map elements

I’m trying to create a list of map elements which fit a particular condition. My code looks like this:

private function myDogs'(all' : list(string * dog), dest' : list(dog), key' : address) : list(dog) = 
    switch(all')
      [] => dest'
      [hd::tl] => 
        if (hd[1].owner == key') 
          myDogs'(tl, dest' ++ [hd[1]], key') 
        else myDogs'(tl, dest', key')
  
  entrypoint myDogs() : list(dog) =
    myDogs'(Map.to_list(state.dogs), [], Call.caller)

Compiling fails with this error:
Error: Http request for https://compiler.aepps.com/compile failed with status code 403. Status: Forbidden. Error data: {"reason":"Parse errors\nline 16, column 47: Unexpected token '*'"}

It’s obviously complaining about this definition in the function’s arguments: all' : list(string * dog), dest'. The reason why I wrote it like this is how the documentation defines the output of Map.to_list:

Map.to_list(m : map('k, 'v)) : list('k * 'v)

Obviously I can’t define my function in the same way. Hence I wonder how I should define my function. Also I wonder whether I’m doing things in the most efficient way. Is there perhaps a better way to get a list of filtered map elements?

* is refering to new sophia syntax that will be released with the new VM in next hard fork. Look at the sophia reference for 4.X.X version of the node: protocol/sophia.md at aeternity-node-v4.2.0 · aeternity/protocol · GitHub

Also maybe a generic filter is better, have a look at: ae-sophia-functions/List.aes at master · thepiwo/ae-sophia-functions · GitHub

Thanks for the fast answer.

I followed your suggestion, the code is now:

private function filter(f : ('a) => bool, l : list('a)) = filter'(f, l, [])
  private function filter'(f : ('a) => bool, l : list('a), acc : list('a)) =
    switch(l)
      [] => acc
      e :: l' =>
        if(f(e))
          filter'(f, l', e :: acc)
        else
          filter'(f, l', acc)
  
  entrypoint myDogs() : list(dog) =
    filter((_ :: dog) : bool = dog.owner == Call.caller, Map.to_list(state.dogs))

The only thing which is still unclear to me is how to write the closure properly in the call to filter. I can’t find anything about closures in Sophia in the documentation.

For your initial example, this is what you meant I think:

  record dog = { owner : address }
  record state = { dogs : map(string, dog) }
  entrypoint init() = { dogs = {} }

  entrypoint myDogs() : list(dog) =
    myDogs'(Map.to_list(state.dogs), [], Call.caller)

  private function myDogs'(all' : list(string * dog), dest' : list(dog), key' : address) : list(dog) =
    switch(all')
      [] => dest'
      (_, dog)::tl =>
        if (dog.owner == key')
          myDogs'(tl, dog :: dest', key')
        else myDogs'(tl, dest', key')

Using List.filter wont get you all the way since you have tuples (string * dog) and you want just a list of dogs?! I.e. you need to do a projection as well.

1 Like

Thank you. Your code makes a lot of things clear to me. The only issue is that the current Sophia editor doesn’t support the definition with the * yet. Is there a way to reach the same effect under the current version of Sophia?

I though to use a tuple instead of the *, but that’s not working. The code is now:

private function myDogs'(all' : list((string, dog)), dest' : list(dog), key' : address) : list(dog) =
    switch(all')
      [] => dest'
      (_, dog)::tl =>
        if (dog.owner == key')
          myDogs'(tl, dog :: dest', key')
        else myDogs'(tl, dest', key')
  
  entrypoint myDogs() : list(dog) =
    myDogs'(Map.to_list(state.dogs), Call.caller)

resulting in the error: Error: Http request for https://compiler.aepps.com/compile failed with status code 403. Status: Forbidden. Error data: {"reason":"Type errors\nCannot unify list(dog)\n and address\nwhen checking the application at line 25, column 5 of\n myDogs' :\n (list((string, dog)), list(dog), address) => list(dog)\nto arguments\n Map.to_list(state.dogs) : list((string, DoggyChain.dog))\n Call.caller : address\n"}

The current compiler supports * for tuples, but you can adjust if your tools doesn’t support it…

You are trying to call a three argument function with two arguments, strangely that doesn’t work… It should be myDogs'(Map.to_list(state.dogs), [], Call.caller) on the last row - just like the compiler tries to tell you.

Damn. I completely looked over the fact that I simply forgot one of the arguments… Thanks for your help!