HigherOrderCO / Bend

A massively parallel, high-level programming language
https://higherorderco.com
Apache License 2.0
17.48k stars 428 forks source link

ERROR: attempt to clone a non-affine global reference #709

Open angeloocana opened 2 months ago

angeloocana commented 2 months ago

Reproducing the behavior

I'm getting the error ERROR: attempt to clone a non-affine global reference. when I call a function to create a Map or List and then pass it to another function. Example:

empty = 0
white = 1
black = 2

def get_initial_board():
  return { 0: black, 1: white, 2: empty }

def get_empty_positions(board):
  # We don't even need to use the board variable, just passing it to this function raises the error
  return [2]

def get_possible_moves(board, pieces):
  fold pieces with pieces_with_possible_moves = []:
    case List/Cons:
      return pieces.tail(List/Cons((pieces.head, get_empty_positions(board)), pieces_with_possible_moves))
    case List/Nil:
      return pieces_with_possible_moves

def main():
  board = get_initial_board() # If I call any function to create the board I get the error
  # board = { 0: black, 1: white, 2: empty } # If I create the board here it works
  pieces = [0]
  possible_moves = get_possible_moves(board, pieces)
  return possible_moves

bend run test.bend -s gives this error:

ERROR: attempt to clone a non-affine global reference.

Errors:
Failed to parse result from HVM.

bend run-c test.bend -s works fine:

Result: [(0, [2])]
- ITRS: 369
- TIME: 0.00s
- MIPS: 0.16

System Settings

Example:

Additional context

No response

developedby commented 2 months ago

get_initial_board gets passed as the argument board and then cloned inside get_possible_moves.

This is one of the cases where the heuristic for finding the problematic cases is too strict. This function has some duplications inside it, but not in its normalized form.

The function desugars to Map/set(Map/set(Map/set(Map/empty, 0, black), 1, white), 2, empty), and Map/set duplicates the key (0, 1 and 2 in this example). However, after normalizing, get_initial_board reduces to just an ADT value and not a function so the duplications are not a problem.

Unfortunately this check happens directly at runtime with no way of disabling it for your specific function. Ideally we'd have a type system that can assertain whether or not a value will have problematic duplications, but that's not planned for the immediate future.

For now, you can circumvent this issue by applying a dummy value to the function that forces its evaluation:

def get_initial_board(dummy):
  return { 0: black, 1: white, 2: empty }

...

def main():
  board = get_initial_board(*)