Reputation: 1949
I fear that it might come out as extremely cringy, but I don't know where else to ask.
For a while I thought if a function accepts another function as parameter it had to specifically say it in the type, i.e f :: (a -> a -> a) -> b -> c
. So that I can pass something like g :: a -> a -> a
as its first paramater and concrete type parameter as second, but nothing else.
But I have just discovered that I can apply const :: a -> b -> a
to (+) :: Num a => a -> a -> a
. And the result is weird (+) const :: Num (a -> b -> a) => (a -> b -> a ) -> a -> b -> a
.
Also how is this foldr :: ( a -> b -> b) ...
taking (\b g x -> g (f x b))
as it's first parameter.
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f a bs =
foldr (\b g x -> g (f x b)) id bs a
What's going on here? I don't even know how to call it, so that I can't do google search or use appropriate title on my question here.
Where can I learn about this? What is this called? What's the application and theory of it?
Upvotes: 1
Views: 136
Reputation: 10618
Any type variable can be instantiated with a function type, too.
The simplest example that gave me an "a ha" moment was to realise that:
($) :: (a -> b) -> (a -> b)
is just a special case of:
id :: a -> a
In other words, you can use id
just like $
, as an infix application operator:
ghci> negate $ 5
-5
ghci> negate `id` 5
-5
ghci> map ($ 5) [negate,(2*),(1+)]
[-5,10,6]
ghci> map (`id` 5) [negate,(2*),(1+)]
[-5,10,6]
You can literally define ($)
as id
, just with the type narrowed down to functions:
($) :: (a -> b) -> a -> b
($) = id
So, in summary, anywhere you see a type variable like a
, you can choose to instantiate it with a function type like (b -> c)
.
Upvotes: 1
Reputation: 120751
There is nothing special about function types. A polymorphic function a -> a
can obviously be used as, say, Int -> Int
, or String -> String
. But it can just as well be used as (Char->Double) -> (Char->Double)
. In the example you've tried, you use basically
(+) :: (a->b->a) -> (a->b->a) -> (a->b->a)
Parentheses on the result can be ommitted, so this is
(+) :: (a->b->a) -> (a->b->a) -> a -> b -> a
If we supply a const
, we then get
(const +) :: (a->b->a) -> a -> b -> a
So far I didn't bother about the Num
constraint. That constraint is actually a problem, though it's not completely nonsensical – it is in principle possible to declare a Num
instance for function types:
instance (Num r) => Num (a->r) where
fromInteger = const . fromInteger
f + g = \x -> f x + g x
negate f = negate . f
...
Then the constraint Num (a->b->a)
would be equivalent to Num (b->a)
, which in turn would be equivalent to Num a
alone. You can then actually use addition of functions like const
: since this is a two-parameter function, the result of the addition will
> (const + \x y -> y) 1 1
2
> (const + \x y -> x*y) 1 0
1
and so on.
However, just because it's possible to define an instance doesn't necessarily mean it's a good idea; it can also lead to weird unexpected behaviour. In particular, the Num
instance for functions fails to fulfill the field axioms. The instance is not defined in the standard library, and IMO this is good. Hence, (+) const
does not actually make any sense in Haskell.
Upvotes: 4