Haskell's (numerical) type system
I'm still having a hard time wrapping my brain around Haskell and its type system.
Well I think that there are some aspects about the numerical types in Haskell:
- there are no implicit conversions between types (not between numerical types, nor between other types), so you can not implicitly convert an
Int
to a Float
;
- most operators work on the same type. For example
(+) :: Num a => a -> a -> a
, so that means if one of the operands is an Int
, then the other operand and the result are all Int
s; and
- the numerical operators (like
(+)
, (-)
, (**)
) originate from type classes. For example (**) :: Fractional a => a -> a -> a
originates from the Fractional
type class.
Deriving the type of the function (and why it is problematic)
Let us now emulate the Haskell compiler. You define a function:
thirdLst n x = zipWith (*) (ymod2 n) (powersOfx (fromIntegral n) x)
So we here see that the function takes two parameters n
, and x
, so first we assume that thirdLst
has type:
thirdLst :: a -> b -> c
but we still need to analyze the types by looking at the expression. We see that the ymod2 :: Integral d => d -> [d]
(we here use another name, since those are basically different variables) function is called, so that means that the type of n
is a
as well as d
, so that means that a
and d
are the same type, and ymod2 n
produces a list [a]
. We also have to add the Integral a
type, and now the type of our function is:
thirdLst :: Integral a => a -> b -> c
ymod2 n :: Integral a => [a]
We also see in the expression that the fromIntegral :: (Integral e, Num f) => e -> f
is called with n
as parameter, so we conclude that a ~ e
(a
and e
are the same type), and that fromIntegral n
has type Num f => f
. We again add an Integral
type constraint to a
, but since it is already constrained that way, the constraints on a
remain the same:
thirdLst :: Integral a => a -> b -> c
ymod2 n :: Integral a => [a]
fromIntegral n :: Num f => f
The fromIntegral
expression is only a subexpression of (powersOfx (fromIntegral n) x)
, since powersOfx
has type powersOfx :: (Enum g, Floating g) => g -> g -> [g]
. We thus know that f ~ g
, and b ~ g
, we thus know that the result of the expression is [b]
, and we have to add Enum b
and Floating b
as constraints:
thirdLst :: (Integral a, Enum b, Floating b) => a -> b -> c
ymod2 n :: Integral a => [a]
fromIntegral n :: Num b => b
powersOfx (fromIntegral n) x :: (Enum b, Floating b) => [b]
This is up to now no problem, the two parameters of thirdLst
, n
and x
have a different type, but now we use zipWith :: (h -> i -> j) -> [h] -> [i] -> [j]
and we use as first argument (*) :: Num k => k -> k -> k
, so as a result we know that k ~ h ~ i ~ j
, and thus that our zipWith (*)
has type:
zipWith (*) :: Num k => [k] -> [k] -> [k]
and now the problem arises, we see that the two arguments of zipWith (*)
need to have the same type. Since ymod2 n :: Integral a => [a]
and powersOfx (fromIntegral n) x :: (Enum b, Floating b) => [b]
are the parameters we use for zipWith (*)
this means that k ~ a ~ b
, so k
, a
and b
are all the same type. So now we conclude:
thirdLst :: (Integral a, Enum a, Floating a, Num a) => a -> a -> [a]
So that means that the two parameters need to have the same type. Now this already results in an important issue: numbers typically are not Integral
and Floating
at the same time. There are in the most common numerical library no number types that are both Floating
and Integral
at the same time. So this makes the function quite useless.
Allowing more freedom
So we will need to find more generic functions, and more freedom. Your powersOfx
function is too restrictive: since the length of the list an x
need to have the same type. We can construct one where the two types are independent:
import Data.List(genericTake)
powersOfx :: (Num a, Integral i) => i -> a -> [a]
powersOfx n x = genericTake (n+1) (iterate (x*) 1)
for ymod2
, you basically construct a list with a certain length that looks like [0, 1, 0, 1, ...]
. So we can use cycle :: [a] -> [a]
for that and again an genericTake (n+1)
:
ymod2 :: (Num a, Integral i) => i -> [a]
ymod2 n = genericTake (n+1) (cycle [0, 1])
and then our function is:
thirdLst :: (Num c, Integral a) => a -> c -> [c]
thirdLst n x = zipWith (*) (ymod2 n) (powersOfx n x)