Consider the function
f g h x y = g (g x) (h y)
What is its type? Obviously I can just use :t f to find out, but if I need to deduce this manually, what's the best way to go about this?
The method I have been shown is to assign types to parameters and deduce from there - e.g. x :: a, y :: b gives us that g :: a -> c and h :: b -> d for some c,d (from g x, h y) and then we keep on making deductions from there (c = a from g (g x) (h y) etc.).
However this sometimes just turns into a huge mess and often I'm not sure how to make further deductions or work out when I'm done. Other problems sometimes happen - for instance, in this case x will turn out to be a function, but that was not obvious to me before cheating and looking up the type.
Is there a specific algorithm that will always work (and is reasonable for a human to execute quickly)? Otherwise, are there some heuristics or tips that I am missing?
Let's inspect the function at the top level:
f g h x y = g (g x) (h y)
We will begin by assigning names to types, then going along and specialising them as we learn more about the function.
Firstly, let's assign a type to the top expression. Let's call it a:
g (g x) (h y) :: a
Let's take the first argument out and assign types respectively:
-- 'expanding' (g (g x)) (h y) :: a
h y :: b
g (g x) :: b -> a
And again
-- 'expanding' g (g x) :: b -> a
g x :: c
g :: c -> b -> a
And again
-- 'expanding' g x :: c
x :: d
g :: d -> c
But hold on: we now have that g :: c -> b -> a and that g :: d -> c. So by inspection, we know that c and d are equivalent (written c ~ d) and also that c ~ b -> a.
This can be inferred by simply comparing the two types for g that we have inferred. Note that this is not a type contradiction, since the type variables are general enough to fit their equivalents. This would be a contradiction if we had inferred, for instance, that Int ~ Bool somewhere.
So we now have the following information in total: (a little work omitted)
y :: e
h :: e -> b
x :: b -> a -- Originally d, applied d ~ b -> a.
g :: (b -> a) -> b -> a -- Originally c -> b -> a, applied c ~ b -> a
This was done by substituting the most specific form of each type variable, that is substituting c and d for the more specific b -> a.
So, simply inspecting which arguments go where, we see that
f :: ((b -> a) -> b -> a) -> (e -> b) -> (b -> a) -> e -> a
This is confirmed by GHC.
Well the function is:
f g h x y = g (g x) (h y)
or more verbose:
f g h x y = (g (g x)) (h y)
Intially we assume that all the four parameters (g, h, x, and y) have different types. We also introduce an output type for our function (here t):
g :: a
h :: b
x :: c
y :: d
f g h x y :: t
But now we are going to perform some inference. We see for example g x, so this means that there is a function application with g the function, and x the parameter. This means that g is a function, with as input type c, so we redefine the type of g to:
g :: a ~ (c -> e)
h :: b
x :: c
y :: d
f g h x y :: t
(here the tilde ~ means that two types are the same, so a is the same as c -> e)).
Since g has type g :: c -> e, and x has type c, this thus means that the result of the function application g x has type g x :: e.
We see another function application, g as function, and g x as argument. So this means that the input type of g (which is c), should be equal to the type of g x (which is e), hence we know that c ~ e, so the types now are:
c ~ e
g :: a ~ (c -> c)
h :: b
x :: c
y :: d
f g h x y :: t
Now we see a function application with h the function, and y the argument. So that means that h is a function, and the input type is the same as the type of y :: d, so h has type d -> f, so that means:
c ~ e
g :: a ~ (c -> c)
h :: b ~ (d -> f)
x :: c
y :: d
f g h x y :: t
finally we see a function application with g (g x) the function, and h y the argument, so that means that the ouput type of g (g x) :: c should be a function, with f as input type, and t as output type, so that means that c ~ (f -> t), and therefore:
c ~ e
c ~ (f -> t)
g :: a ~ (c -> c) ~ ((f -> t) -> (f -> t))
h :: b ~ (d -> f)
x :: (f -> t)
y :: d
f g h x y :: t
So that means that since f has those parameters g, h, x and y, the type of f is:
f :: ((f -> t) -> (f -> t)) -> (d -> f) -> (f -> t) -> d -> t
-- \_________ __________/ \__ ___/ \__ ___/ |
-- v v v |
-- g h x y
You already described how to do it, but maybe you missed the unification step. That is, sometimes we know that two variables are the same:
x :: a
y :: b
g :: a -> b -- from g x
h :: c -> d -- from h y
a ~ b -- from g (g x)
We know that a and b are the same, because we passed g x, a b, to g, which expects an a. So now we replace all the bs with a, and keep going until we have considered all subexpressions...
With regard to your "huge mess" comment, I have a couple things to say:
This is the way to do it. If it's too hard, you just need to practice, and it will get easier. You will start to develop an intuition and it will come more easily.
This particular function is not an easy function to do. I've been programming Haskell for 12 years and I still need to go through the unification algorithm on paper for this one. The fact that it is so abstract doesn't help -- if I knew what this function's purpose was it would be much easer.
Simply write down all the entities' types under them:
f g h x y = g (g x) (h y)
x :: x y :: y
h :: y -> a , h y :: a
g :: x -> b , g x :: b
g :: b -> (a -> t) , x ~ b , b ~ (a -> t)
f :: (x -> b) -> (y -> a) -> x -> y -> t , x ~ b , b ~ (a -> t)
f :: (b -> b) -> (y -> a) -> b -> y -> t , b ~ (a -> t)
-- g h x y
Thus f :: ((a -> t) -> (a -> t)) -> (y -> a) -> (a -> t) -> y -> t. That's all.
Indeed,
~> :t let f g h x y = g (g x) (h y) in f
:: ((t1 -> t) -> t1 -> t) -> (t2 -> t1) -> (t1 -> t) -> t2 -> t
This goes like this:
x must have some type, let's call it x: x :: x.
y must have some type, let's call it y: y :: y.
h y must have some type, let's call it a: h y :: a. hence h :: y -> a.
g x must have some type, let's call it b: g x :: b. hence g :: x -> b.
g _ _ must have some type, let's call it t. hence g :: b -> a -> t.
which is the same as g :: b -> (a -> t).
the two type signatures for g must unify, i.e. be the same under some substitution of type variables involved, since the two signatures describe the same entity, g.
thus we must have x ~ b, b ~ (a -> t). This is the substitution.
Having all the types of the arguments to f, we know it produces what g produces, i.e. t. So we can write down its type, (x -> b) -> (y -> a) -> x -> y -> t.
Lastly, we substitute the types according to the substitution, to reduce the number of type variables involved. Thus we substitute b for x first, and then a -> t for b, each time removing the eliminated type variable from the substitution.
When the substitution is empty, we are DONE.
Of course we could have chosen to replace b with x at first, ending up with the substitution x ~ (a -> t), and then we'd end up with the same type in the end, if we always replace the simpler types with the more complex ones (like, replacing b with (a -> t), and not vice versa).
Simple steps, guaranteed results.
Here's another attempt at shorter / clearer derivation. We focus on the fact that g x serves as g's argument, thus g x :: x (and the trivial part still remains, h y :: a):
f g h x y = g (g x) (h y) {- g :: g , h :: h , x :: x , y :: y
g h x y x y , g x :: x -- !
x a t , g x a :: t
x a :: t ... x ~ a->t
f :: g ->h ->x ->y->t
f :: (x ->x )->(y->a)->x ->y->t
f :: ((a->t)->a->t)->(y->a)->(a->t)->y->t -}
Pretty simple after all.
The last argument in the definition can be elided, as f g h x = (g . g) x . h.
Related
I struggle to understand how type declarations work...
Like these ones for example:
t :: (a -> b) -> (b -> c) -> a -> c
s :: (a -> b -> c) -> (a -> b) -> a -> c
I know from just trying different things that the correct functions would look like this:
t :: (a -> b) -> (b -> c) -> a -> c
t f g x = g (f x)
s :: (a -> b -> c) -> (a -> b) -> a -> c
s f g x = f x (g x)
But how does this work? Why are the brackets at the end? Why is it not
t f g x = (f x) g
or
s f g x = (f x) g x
Im so lost
For the first example:
t :: (a -> b) -> (b -> c) -> a -> c
In a type declaration, the type1 -> type2 pattern indicates a function from type1 to type2. In type declarations, the -> operator is right-associative, so this is parsed as:
t :: (a -> b) -> ((b -> c) -> (a -> c))
This kind of construction is called "currying": providing the first argument (type a -> b) yields a function which accepts the second argument (type b -> c) which yields a function which accepts the third argument (type a).
The function declaration syntax is set up to do this automatically. The first two arguments are functions and the third is just a, so start with names that reflect that: f :: a -> b and g :: b -> c are functions, while x :: a is a fully generic type which could be anything.
t f g x = ...
Note that function application in Haskell is just concatenation: to apply function f to value x, just use f x. This syntax is left-associative, so t f g x is parsed as (((t f) g) x) to match the currying construction described above.
Anyway, given these types, you don't have much choice in how to put them together:
the only thing you know about the type a is that it's the type of x, and the argument type of f, so the only thing you can do with them is to apply the function to the value: f x.
the only thing you know about the type b is that it's the result type of f and the argument type of g, so the only thing you can do is apply g (f x).
the only thing you know about the type c is that it's the result type of g and of the overall function t, so the only thing t can return is g (f x).
The reason you can't do (f x) g is that the types don't match:
f :: a -> b
x :: a
(f x) :: b
g :: b -> c
So, you can apply g :: b -> c to (f x) :: b to get a result of type c. But not the other way around, because b might not even be a function type.
I'm having trouble understanding whats going on in this function. My understanding is that fmap f x returns a function that takes the last argument y. But when is y "fed" to fmap f x inside the case statement?.
func :: (Num a, Num b) => (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
func f x y = case fmap f x of
Nothing -> Nothing
Just z -> fmap z y
For Maybe, the functor instance is defined as:
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just x) = Just (f x)
So for f ~ Maybe, fmap is specialized to fmap :: (g -> h) -> Maybe g -> Maybe h.
In your specific case, f has signature f :: a -> b -> c, or more verbose ff :: a -> (b -> c). So that means that for our signature of fmap, we obtain:
fmap :: (g -> h ) -> Maybe g -> Maybe h
f :: a -> (b -> c)
----------------------------------------------
g ~ a, h ~ (b -> c)
So that means fmap f x will have type fmap f x :: Maybe (b -> c). It is thus a Maybe that wraps a function of type b -> c.
Here we thus can inspect if fmap f x is a Just z, in which case z has type z :: b -> c, or Nothing.
In case it is a Just z, we thus can perform another fmap with z, and thus obtain a Maybe c.
I have two functions
(\f b -> (\a -> a) f b b)
and
(\f b -> (\a -> 0) f b b)
I tried to find the type of these functions by hand and got
(t1 -> t1 -> t2) -> t1 -> t2
and
Num t3 => (t1 -> t1 -> t2) -> t1 -> t3
But when I use GHCI to get the type using :t I get the following
(\f b -> (\a -> a) f b b) :: (t1 -> t1 -> t2) -> t1 -> t2
(\f b -> (\a -> 0) f b b) :: Num (t1 -> t1 -> t2) => p -> t1 -> t2
I don't understand how changing \a -> a to \a -> 0 changes the first parameter from (t1 -> t1 -> t2) to p
Deriving the type for (\f b -> (\a -> a) f b b)
Well let us try to derive the type for the expression:
(\f b -> (\a -> a) f b b)
or more verbose:
(\f -> (\b -> (((\a -> a) f) b) b))
We here see that this is a function taking two parameters (well technically speaking a function always takes one parameter, and the result of that function then can take another one, but let us assume that if we talk about "two parameters", we mean such construct).
The parameters are thus f, and b, and initially we do not know much about these, so we assign them a type, and the expression is:
f :: g
b :: h
(\f b -> (\a -> a) f b b) :: g -> (h -> i)
We thus create three types g, h and i (I here used other identifiers than a, b and c, since that could introduce confusion with the variables).
But we are not done yet, since the expression itself, can introduce more constraints on how the types behave. We see for example a lambda expression: \a -> a, this clearly has as type:
\a -> a :: j -> j
Next we see a function application, with \a -> a as function, and f as argument, so that means that g ~ j (g and j are the same type), and the type of (\a -> a) f is (\a -> a) f :: g.
But we are not done yet, since the result of (\a -> a) f, now acts as a function in a function application with b, so that means that g is in fact a function, with input type h, and some (currently unknown output type), so:
g ~ (h -> k)
So the type of (\a -> a) f b is k, but again we are not done yet, since we perform another function application with (\a -> a) f b as function (type k), and b as parameter, so that means that k is in fact a function, with h as parameter type, and the result is the type of the expression, so i. So that means we have:
g ~ j
g ~ (h -> k)
k ~ (h -> i)
In other words, the type of the expression is:
(\f b -> (\a -> a) f b b) :: (h -> (h -> i)) -> (h -> i)
or less verbose:
(\f b -> (\a -> a) f b b) :: (h -> h -> i) -> h -> i
Deriving the type for (\f b -> (\a -> 0) f b b)
The first steps of the derivation are more or less the same, we first introduce some type variables:
f :: g
b :: h
(\f b -> (\a -> 0) f b b) :: g -> (h -> i)
and now we start doing the inference. We first infer the type of (\a -> 0). This is a function, with type Num l => j -> l since 0 is a Number, but it can be any Num type, and has nothing to do with the type of the parameter a.
Next we see that there is a function call with (\a -> 0) as function, and f as parameter, we thus conclude that g ~ j. The type of the result of this function call is (\a -> 0) f :: Num l => l.
Now we see another function call with (\a -> 0) f as function, and b as parameter. We thus conclude that l is a function (so l ~ (h -> k)).
The last function call is with (\a -> 0) f b :: k as function, and b again as parameter. This means that k is a function k ~ h -> i. We thus obtain the following types and equalities:
f :: g
b :: h
(\a -> 0) :: Num l => j -> l
(\f b -> (\a -> 0) f b b) :: g -> (h -> i)
g ~ j
l ~ (h -> k)
k ~ (h -> i)
The type of the expression is thus:
(\f b -> (\a -> 0) f b b) :: g -> (h -> i)
or more specific:
(\f b -> (\a -> 0) f b b) :: Num (h -> (h -> i)) => g -> (h -> i)
or less verbose:
(\f b -> (\a -> 0) f b b) :: Num (h -> h -> i) => g -> h -> i
So since we use as inner lambda expression (\a -> 0), nor the type nor the value of f, are relevant anymore. (\a -> 0) f will always return 0, and this should be a function, that can take a b into account.
At least from a theoretical point of view, there is nothing "strange" about a function that is a Num (as long as it supports the functions that should be implemented by Num types). We could for example implement a function instance Num (a -> b -> Int), and thus see 0 as a constant function that always maps to 0, and (+) as a way to construct a new function that adds the two functions together.
Recently I've finally started to feel like I understand catamorphisms. I wrote some about them in a recent answer, but briefly I would say a catamorphism for a type abstracts over the process of recursively traversing a value of that type, with the pattern matches on that type reified into one function for each constructor the type has. While I would welcome any corrections on this point or on the longer version in the answer of mine linked above, I think I have this more or less down and that is not the subject of this question, just some background.
Once I realized that the functions you pass to a catamorphism correspond exactly to the type's constructors, and the arguments of those functions likewise correspond to the types of those constructors' fields, it all suddenly feels quite mechanical and I don't see where there is any wiggle room for alternate implementations.
For example, I just made up this silly type, with no real concept of what its structure "means", and derived a catamorphism for it. I don't see any other way I could define a general-purpose fold over this type:
data X a b f = A Int b
| B
| C (f a) (X a b f)
| D a
xCata :: (Int -> b -> r)
-> r
-> (f a -> r -> r)
-> (a -> r)
-> X a b f
-> r
xCata a b c d v = case v of
A i x -> a i x
B -> b
C f x -> c f (xCata a b c d x)
D x -> d x
My question is, does every type have a unique catamorphism (up to argument reordering)? Or are there counterexamples: types for which no catamorphism can be defined, or types for which two distinct but equally reasonable catamorphisms exist? If there are no counterexamples (i.e., the catamorphism for a type is unique and trivially derivable), is it possible to get GHC to derive some sort of typeclass for me that does this drudgework automatically?
The catamorphism associated to a recursive type can be derived mechanically.
Suppose you have a recursively defined type, having multiple constructors, each one with its own arity. I'll borrow OP's example.
data X a b f = A Int b
| B
| C (f a) (X a b f)
| D a
Then, we can rewrite the same type by forcing each arity to be one, uncurrying everything. Arity zero (B) becomes one if we add a unit type ().
data X a b f = A (Int, b)
| B ()
| C (f a, X a b f)
| D a
Then, we can reduce the number of constructors to one, exploiting Either instead of multiple constructors. Below, we just write infix + instead of Either for brevity.
data X a b f = X ((Int, b) + () + (f a, X a b f) + a)
At the term-level, we know we can rewrite any recursive definition
as the form x = f x where f w = ..., writing an explicit fixed point equation x = f x. At the type-level, we can use the same method
to refector recursive types.
data X a b f = X (F (X a b f)) -- fixed point equation
data F a b f w = F ((Int, b) + () + (f a, w) + a)
Now, we note that we can autoderive a functor instance.
deriving instance Functor (F a b f)
This is possible because in the original type each recursive reference only occurred in positive position. If this does not hold, making F a b f not a functor, then we can't have a catamorphism.
Finally, we can write the type of cata as follows:
cata :: (F a b f w -> w) -> X a b f -> w
Is this the OP's xCata type? It is. We only have to apply a few type isomorphisms. We use the following algebraic laws:
1) (a,b) -> c ~= a -> b -> c (currying)
2) (a+b) -> c ~= (a -> c, b -> c)
3) () -> c ~= c
By the way, it's easy to remember these isomorphisms if we write (a,b) as a product a*b, unit () as1, and a->b as a power b^a. Indeed they become
c^(a*b) = (c^a)^b
c^(a+b) = c^a*c^b
c^1 = c
Anyway, let's start to rewrite the F a b f w -> w part, only
F a b f w -> w
=~ (def F)
((Int, b) + () + (f a, w) + a) -> w
=~ (2)
((Int, b) -> w, () -> w, (f a, w) -> w, a -> w)
=~ (3)
((Int, b) -> w, w, (f a, w) -> w, a -> w)
=~ (1)
(Int -> b -> w, w, f a -> w -> w, a -> w)
Let's consider the full type now:
cata :: (F a b f w -> w) -> X a b f -> w
~= (above)
(Int -> b -> w, w, f a -> w -> w, a -> w) -> X a b f -> w
~= (1)
(Int -> b -> w)
-> w
-> (f a -> w -> w)
-> (a -> w)
-> X a b f
-> w
Which is indeed (renaming w=r) the wanted type
xCata :: (Int -> b -> r)
-> r
-> (f a -> r -> r)
-> (a -> r)
-> X a b f
-> r
The "standard" implementation of cata is
cata g = wrap . fmap (cata g) . unwrap
where unwrap (X y) = y
wrap y = X y
It takes some effort to understand due to its generality, but this is indeed the intended one.
About automation: yes, this can be automatized, at least in part.
There is the package recursion-schemes on hackage which allows
one to write something like
type X a b f = Fix (F a f b)
data F a b f w = ... -- you can use the actual constructors here
deriving Functor
-- use cata here
Example:
import Data.Functor.Foldable hiding (Nil, Cons)
data ListF a k = NilF | ConsF a k deriving Functor
type List a = Fix (ListF a)
-- helper patterns, so that we can avoid to match the Fix
-- newtype constructor explicitly
pattern Nil = Fix NilF
pattern Cons a as = Fix (ConsF a as)
-- normal recursion
sumList1 :: Num a => List a -> a
sumList1 Nil = 0
sumList1 (Cons a as) = a + sumList1 as
-- with cata
sumList2 :: forall a. Num a => List a -> a
sumList2 = cata h
where
h :: ListF a a -> a
h NilF = 0
h (ConsF a s) = a + s
-- with LambdaCase
sumList3 :: Num a => List a -> a
sumList3 = cata $ \case
NilF -> 0
ConsF a s -> a + s
A catamorphism (if it exists) is unique by definition. In category theory a catamorphism denotes the unique homomorphism from an initial algebra into some other algebra. To the best of my knowledge in Haskell all catamorphisms exists because Haskell's types form a Cartesian Closed Category where terminal objects, all products, sums and exponentials exist. See also Bartosz Milewski's blog post about F-algebras, which gives a good introduction to the topic.
It is not clear to me why the function defined as
f g x = g . g x
has the type
f :: (b -> a -> b) -> b -> a -> a -> b
I would have thought it would be of type
f :: (t -> t) -> t -> t
Can anyone explain to me how the expression is broken down? Thanks!
Note that function application has the highest priority; operators come later.
So, the term g . g x first applies g to x, and then composes the result and g itself. If x has type b, g must have type b -> c. Since we compose g with g x (the latter of type c), c must be a function type returning b, so c = a -> b. Now, the type of g is b -> a -> b and the type of g . g x is a -> (a -> b); the type of f happens to be (b -> a -> b) -> b -> a -> a -> b.
If you wanted something like (a -> a) -> a -> a instead, you could try one of this
f g x = g (g x)
f g x = (g . g) x
f g x = g . g $ x
f g = g . g
The idea is about understanding the (.) operator, it has a type of
(.) :: (b -> c) -> (a -> b) -> a -> c
It takes two functions each with one parameter and compose them, after applying g x the compiler assumed g is actually g :: a -> b -> c in order to satisfy the signature of (.) :: (b -> c) -> (a -> b) -> a -> c which takes two functions with one argument. Otherwise the code won't compile.
And finally if you want the signature f :: (t -> t) -> t -> t you need something like this:
λ> let applyTwice g = g.g
λ> :t applyTwice
applyTwice :: (a -> a) -> a -> a
λ> applyTwice (*2) 3
12