Skip to content

borrowing in a high order function creeps internal details into the caller #859

@kikofernandez

Description

@kikofernandez

Hi,

I was trying to use a simple high-order function but I am not sure I got this correctly. The original function is here:

fun filter[a](fn : a -> bool, ps : Par[a]) : Par[a]
  join(ps >> fun (item : a) => if fn(item) then
                                 liftv(item)
                               else
                                 empty[a]()
                               end)
end

However, I need to work with linear a;I have added the linear a type variable to the function and I also also use a borrow block in the internal function to be able to alias the linear item; after the borrow block I expected to get back my normal linear item:

fun filterL[linear a](fn : a -> bool, ps : Par[a]) : Par[a]
  joinL(ps >> fun (item : a)
                var b = false
                borrow item as borrowItem in
                  if fn(borrowItem) then
                    b = true
                  end
                end
                if b then
                  liftvL(item)
                else
                  emptyL[a]()
                end
              end)
end

However, this throws the typing error:

"ParT.enc" (line 69, column 22)
Type 'borrowed linear a' does not match expected type 'linear a'
In expression: 
  fn(borrowItem)

Since it does not allow me to use the function fn without adding the borrowed annotation, an internal detail in the implementation pollutes the top level signature of the function. The end result is:

fun filterL[linear a](fn : borrowed a -> bool, ps : Par[a]) : Par[a]
  joinL(ps >> fun (item : a)
                var b = false
                borrow item as borrowItem in
                  if fn(borrowItem) then
                    b = true
                  end
                end
                if b then
                  liftvL(item)
                else
                  emptyL[a]()
                end
              end)
end

This however does not compile because the type parameter a in ps >> fun (item : a) now needs to have the borrowed annotation as well. Ok, so I update the code to (addition of borrowed in the lambda function):

fun filterL[linear a](fn : borrowed a -> bool, ps : Par[a]) : Par[a]
  joinL(ps >> fun (item : borrowed a)
                if fn(item) then
                  liftvL(item)
                else
                  emptyL[a]()
                end
              end)
end

Now, it complains that liftvL:

"ParT.enc" (line 68, column 19)
Type 'borrowed linear a' does not match expected type 'linear a'

I do understand the message but at this pace, a combinator that wants to work with read and linear modes needs to implement at least three functions:

  • one function that takes a linear thing
  • one function that takes a linear borrowed thing
  • one function that uses the sharable annotation for read

My questions given the reasoning above are:

  1. Is there anything that I can do to the simple filter combinator that would give me the semantics that I want?
  2. If not, is there anything that we can do to improve re-using code without having to write at least three functions?
  3. At another level, I do understand that a function that is given a linear argument that has been borrowed has the linear borrowed annotation. However, for functions, isn't this exposing and tying the internal implementation details to top level types?
  4. Can we do some kind of static analysis whenever we have a high-order function that receives a linear type so that it checks if it is possible to use the borrowed thing without having to "creep" the internal implementation to the top level types in the high-order function?

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions