Reputation: 2554
I'm quite new to haskell and not too comfortable with it's type system yet. And I wondering, if there is ability to define the type (datatype?), which instances can be called as functions?
Analogues are
__call__
method in Python or class method
operator()
in c++. (More examples are given in wikipedia for the word "Function object").
The example of application of such a construct is Polynom. The object is defined by list of its coefficients, i.e. I would like to have type like this:
data Num a => Polynom a = Polynom [a]
deriving (...)
Now of course I can define function
callPoly :: Num a => (Polynom a) -> a -> a
callPoly p x = ... -- implementation: extract coefficients of p,
-- construct polynomial and call it on x
(here I do not bother, to be able to call polynom with Int coefficients on Floats... it's just techincal details)
Now I can call it on my polinomial (in interactive prompt):
let myPoly = Polynomial [1 2 3]
let applicationResult = callPoly myPoly 3
But this way is not too fancy. Is is desirable to be able to call polynomial directly as
let applicationResult = myPoly 3
So the question is: It is possible to define such Polynomial type, which objects (instances) can be called (used as functions)? May be this pattern may be implemented in some other way, not involving 'data'? May be some playing with function types or smth. else?
Of course, this idea can be applied not only to polynomials. Actually, to any function, which must "has type" and has "some attached data" (in case of polynomial - it is coefficients).
Or if this is not possible, then is there some specific reason for this or it is just not supported?
P.S.: It seems to me, that direct approach (as described above) is impossible, because to be callable myPoly must be of type (Int -> Int). But type (Int -> Int) cannot have any data attached (i.g. polynomial coefficients). But I want to make sure, that I'm right.
Upvotes: 11
Views: 918
Reputation: 8266
It's good that you're familiar with the C++ "function object" concept, because that's a good introduction to Haskell's idea of what you can do with plain old functions... Specifically, currying, partial application and passing functions as arguments to other functions.
In C++, your code would look something like:
class Polynomial {
int coefficients[];
public:
Polynomial(int coefficients[]) { /* ... */ }
int operator() (int value) { /* ... */ }
};
int coefficients[] = {1, 2, 3};
Polynomial(coefficients)(4); // note the syntax here!
This is fundamentally expressing a single, pure function: a polynomial evaluator that takes a list of coefficients and a value. It could just as easily have been expressed in C++ as:
int evaluatePolynomial(int coefficients[], int value);
int coefficients[] = {1, 2, 3};
evaluatePolynomial(coefficients, 4);
But this form isn't curried as the previous form is. The nice thing about the curried form is you can say:
Polynomial p = Polynomial(coefficients);
p(4);
p(5);
p(6);
instead of:
evaluatePolynomial(coefficients, 4);
evaluatePolynomial(coefficients, 5);
evaluatePolynomial(coefficients, 6);
Okay. So we're thinking of this "function object" thing as an object-oriented programming concept -- an object that masquerades as a function -- but let's forget objects now. If you look at it in Haskell, it's just a function and doesn't require any user-defined datatypes to express nicely:
polynomial :: Num a => [a] -> a -> a
You can call it "normally" (as with evaluatePolynomial()
above), applying it to both arguments at once:
polynomial [1, 2, 3] 4
but because Haskell functions are curried, you can partially apply (as with the Polynomial
function object):
do
let p = polynomial [1, 2, 3]
print (p 4)
print (p 5)
print (p 6)
Easy peasy. Now, if you want to do something closer to C++ where you've got a specific data type representing your Polynomial
function object, you can do that...
newtype Polynomial a = P (a -> a) -- function object
mkPolynomial :: Num a => [a] -> Polynomial a -- constructor
... but that extra complexity doesn't really offer any benefit. You immediately notice that there's nothing special about Polynomial
, it just wraps a regular function, so you end up having to just unwrap it again like:
do
let (P p) = mkPolynomial [1, 2, 3]
print (p 4)
print (p 5)
print (p 6)
In short, the more you frame your thinking purely in terms of functions, rather than objects, the simpler and more idiomatic your Haskell code will end up being.
Upvotes: 8
Reputation: 69934
Yes, in Haskell you can't have callable objects as that would mess up type-inference a lot. You will have to give explicit names to your functions, just like in OO languages that don't support __call__
and need explicit method names.
On the other hand, partial function application and closures make it very easy to obtain a polynomial-function from a polynomial-representation so the limitation is not that bad.
let polyF = callPoly myPoly in
(polyF 17) + (polyF 42)
Upvotes: 4
Reputation: 16107
I may get my terminology wrong here, as I am no Haskell veteran myself. However, from my understanding of Haskell:
As Haskell is not object-oriented, it does not have objects or instances (in the traditional sense, that is). Instead of instances of a datatype, you have a values of the datatype. That being said, since functions are data (values) just like integers and strings, you can have values that are callable in the sense that they can carry their own context (like an instance in OO world would).
If your goal is to have a value you pass around that carries the PolyNom a
, you could simply partially evaluate your function callPoly
, then think of that as your "Callable PolyNom". Example:
myPoly = PolyNom [1, 2, 3]
callMyPoly = callPoly myPoly
-- or simply
callMyPoly = callPoly (PolyNom [1, 2, 3])
Now, the type of callMyPoly is:
callMyPoly :: Num a => a -> a
and you can call it like this:
callMyPoly 5
which is equivalent to:
callPoly myPoly 5
Upvotes: 4