Hacker News new | ask | show | jobs
by anextio 2325 days ago
callAsFunction() isn't really intended to be used in a type like Dice there. It's a bad example.

It's for Swift for TensorFlow's integration with Python, in which a bridged Python object can be called as a function. Functions are, of course, objects in Python, so that makes sense, but it would be a pain to have to call fn.call(bar) instead of fn(bar) when fn is a reference to a python object that is a function in its runtime.

It's also used in TensorFlow models, in which callAsFunction() is usually implemented as passing the model's input through all the defined layers in it.

This makes it easy to treat models as if they were functions, which they mathematically are, unlike Dice.

Example:

    struct MLP: Layer {
        var layer1 = Dense<Float>(inputSize: 2, outputSize: 10, activation: relu)
        var layer2 = Dense<Float>(inputSize: 10, outputSize: 1, activation: relu)
    
        @differentiable
        func callAsFunction(_ input: Tensor<Float>) -> Tensor<Float> {
            let h0 = layer1(input).withDerivative { print("∂L/∂layer1 =", $0) }
            return layer2(h0)
        }
    }

(from: https://www.tensorflow.org/swift/tutorials/custom_differenti... )
3 comments

>It's a bad example.

It's a bad example enabled by a bad language feature. Just you wait until answers on stack overflow, sample code, and open source libs are littered with these "bad examples".

Meanwhile, this example is terrible. There is no real need for callAsFunction in your example when you could have added a simple named function on the object to do so. Better yet, you could define a protocol and make sure your layer conforms to said protocol.

There is an argument to be made against features that are more likely to cause harm than good (i.e. "footguns"), but I don't think this example of using it poorly sheds any light on whether that's the case here. You could, with roughly the same level of rigor, argue that allowing people to name variables is bad by constructing an example with variables named CONFUSING, CONFUSlNG and CONFUS1NG.

Any feature, no matter how good, can be misused. Pointing that out about a specific feature isn't interesting, because it is true of every feature. And the absence of nearly any feature can be worked around, as ultimately all these features compile down to machine code, which doesn't have most features of a high-level language, so the existence of a workaround is also not a very compelling argument.

Scala has the same thing with apply() and it works great, allowing the language to have a consistant syntax for function calls, object creation, and array index access.
What's the difference between a callable object and a closure? I don't see why a language needs both.
Closures are completely unnecessary to start with yet I don’t see you bemoan their existence.

“Callability” not being restricted to functions (closures or not) can often be convenient, especially when that use is colloquial (eg closure where symbols and collections are callable), and obviate unnecessary boilerplate.

What unnecessary boilerplate is obviated by callable objects in a language that already has closures?
Having to wrap every use of whatever method in a closure when you could just pass the object itself eg

    (map (fn [x] (get m x)) a-vec)
Would usually be

    (map m a-vec)
That's not a fair comparison. You don't have to wrap every use of your method in a closure any more than you have to call the constructor of your callable class on every use.

You have to bind your state to a function exactly once in both cases.

Your example just happens to omit the constructor call required to create the callable object while you are showing the code to create the closure.

How are closures unnecessary?
The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.

From: http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...

Anything you do with a closure, you can do with an object that holds the closed-over state and a function that performs the same operation on that state. For a real-world example of this in action, partial application is traditionally associated with closures, but Python's functools.partial() is implemented as a callable object that holds the partially applied function and parameters in instance variables.
> Functions are, of course, objects in Python, so that makes sense

Functions are objects in many langages. Python’s specific feature is that there are callable a beyond functions. A class is a callable and a user-defined object can be one despite neither being functions.

Clojure also has that concept.