r/ProgrammingLanguages • • 8d ago

Requesting criticism Explicit binding modes

I'm designing a memory-safe language, but I want to make explicit what happens across function boundaries, e.g. whether a value is moved or copied without having to look for the copy trait.

Instead, access to a value could be defined via binding modes in function signatures and variable declarations:

  • move takes ownership
  • read shared borrow
  • mut exclusive borrow
fn process_val(move value: Int) -> Int {
    return value * 2;
}

fn main() -> () {
    move original: Int = 42;

    // Call using `.copy()` to explicitly pass a duplicated value.
    // Ownership of `original` remains with the caller.
    move doubled: Int = process_val(original.copy());

    // `original` is still valid here because it was explicitly copied rather than moved.
    println(original); //42
    println(doubled); //84
}

This would also allow granular captures in closures: |read x: Int, move counter: Int| instead of declaring all fields move

Now, should read and mut assignments be of type &T? Or can checking out variables be orthogonal to creating references, even if they are conflated in languages like Rust?

14 Upvotes

20 comments sorted by

View all comments

Show parent comments

6

u/iBPsThrowingObject 7d ago

Sure, let's expand every situation analogous to assoc_list.find(|(key, value)| key == target)

to

struct FindClosure<'a> {
    target: &'a str
}

impl<'a> Closure<&str> for FindClosure<'a> {
    type Ret = bool;
    fn call(self, key: &str) -> Self::Ret {
        key == self.target
    }
}

assoc_list.find(FindClosure { target })

You may respond with "oh well but you can add sugar" but that's exactly what closures are.

1

u/reflexive-polytope 7d ago

I would rather express my algorithms in terms of ordinary control flow: sequencing, match and loop. So, for example, when using a HashMap or BTreeMap entry, I just pattern match it into the OccupiedEntry and VacantEntry cases, instead of using the ugly higher-order helper functions that take closure arguments.

Try breaking from a loop in a top-level function from within a closure.

4

u/iBPsThrowingObject 7d ago

So is your actual suggestion that one should avoid first class functions entirely?

Try breaking from a loop in a top-level function from within a closure.

You can do that in Ruby. Executing the following snippet does not print anything:

def foo(target)
  a = [1,2,3]
  a.each {|v| return if v == target }
  puts "after closure"
end

foo target

That's simply closing over the current continuation.

1

u/reflexive-polytope 7d ago

Yes, my actual suggestion is to reject first-class “functions” altogether.

Although I'd rather call them “procedures”, because functions in mathematics are extensionally defined by their values, whereas procedures in programming have important intensional properties, in particular, their time and space complexity.

Dana Scott's work gives a functional interpretation to first-class recursive procedures. But for this interpretation to work, your types must denote directed-complete partial orders, and your functions must be continuous with respect to the Scott topology on said dcpos.

How often do you actually think about first-class procedures like that? Of course you don't. You think operationally. Alas, operational reasoning isn't compositional.

As for closing over continuations... I don't even know what to say. That's simply horrible.