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.
() is very often used as the result of something that has no interesting result. For example, an IO action that is supposed to perform some I/O and terminate without producing a result will typically have type IO () .
Haskell isn't an object-oriented language. All of the functionality built here from scratch already exists in a much more powerful form, using Haskell's type system.
Haskell classes are roughly similar to a Java interface. Like an interface declaration, a Haskell class declaration defines a protocol for using an object rather than defining an object itself.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With