In the following example I'm internally implementing the function f
. Its signature is using a
as if the type variable a
is scoped, and it is working without giving me a compile error even though I haven't enabled the extension ScopedTypeVariables
:
foo :: Int -> [a] -> [a]
foo n list = snd $ foldl f (1,[]) list where
f :: (Int,[a]) -> a -> (Int,[a])
f (k,l) r
| k==n = (1,l++[r])
| otherwise = (k+1,l)
I was expecting the error: "Couldn't match type ‘a1’ with ‘a’ ...."
because, as I said, I was treating the type variable a
as if it's scoped even though it is not scoped.
I know what a rigid type is, and I already know why the error appears. My question is why I'm not getting the error here.
Ok, there isn’t really a type of variable called Error. But in the same place as where Power Apps would tell you that you have a text variable it will also tell you that your variable has an error. When you look in your app you might find a perfectly valid expression showing you the red lines.
In statistical hypothesis testing, a Type I error is essentially the rejection of the true null hypothesis. The type I error is also known as the false positive error.
You can change the type of the variable and when this happens by accident you might break some code in other places. Just to be clear it is not a good idea to use the same variable for different types of data! Ok, there isn’t really a type of variable called Error.
Incompatible Type errors are like apples and pears. Things just aren’t all the same and you can expect to get some troubles. What is a definition? In Power Apps there are different types of variables. You can have Text, Numbers or even Collections or Tables.
The function
f :: (Int,[a]) -> a -> (Int,[a])
f (k,l) r
| k==n = (1,l++[r])
| otherwise = (k+1,l)
is general enough to type check on its own (assuming n::Int
). The type a
here can indeed be any type -- there's no need to assume that a
is the same a
as the one in the signature of foo
, so no error is generated.
Put in different terms, we could rename a
into b
without any harm:
foo :: Int -> [a] -> [a]
foo n list = snd $ foldl f (1,[]) list where
f :: (Int,[b]) -> b -> (Int,[b])
f (k,l) r
| k==n = (1,l++[r])
| otherwise = (k+1,l)
Here's a simpler example to stress the point. This compiles without errors:
foo :: a -> [a]
foo x = [f x]
where
f :: a -> a -- this could also be b->b
f y = y
By contrast, changing the last line to f y = x
would immediately trigger the error since x
has type "the a
in the signature of foo
", not "the a
in the signature of f
", and the two type variables can't be unified since they are both rigid.
foo :: Int -> [a] -> [a]
foo n list = snd $ foldl f (1,[]) list where
f :: (Int,[a]) -> a -> (Int,[a])
f (k,l) r
| k==n = (1,l++[r])
| otherwise = (k+1,l)
Notice how your inner function f
only uses n
from the outer function's scope. n
is of type Int
; only list
(and foo
itself) are names whose type has anything to do with the a
variable in the type of foo
. Since f
doesn't use those, it doesn't need to have a type that uses ScopedTypeVariables
to refer to the a
from the type of foo
.
In effet, f
is fully polymorphic on its own; it can work with its own a
instantiated to any type. Because it's only used in one place (inside foo
) it will only ever actually be used with its a
instantiated to the a
from the outer foo
. But that happens by exactly the same process any polymorphic type is instantiated in Haskell; the definition of f
has to treat a
as a black box and work with any possible type, and the place where f
is used can instantiate it at anything (in this case, the a
from the type of foo
).
Lets show that more explicitly by using explicit foralls and not giving the same name to different type variables:
foo :: forall a. Int -> [a] -> [a]
foo n list = snd $ foldl f (1,[]) list where
f :: forall b. (Int,[b]) -> b -> (Int,[b])
f (k,l) r
| k==n = (1,l++[r])
| otherwise = (k+1,l)
f
works forall b
, and its only a coincidence of one particular usage that wants to instantiate b ~ a
(even if that "one particular usage" is in fact the only usage).
The typical error in trying to write a type of a local function (that needs ScopedTypeVariables
to resolve) is when the inner function uses something from the outer function that has a variable in its type. Say, something like this:
bar :: (a -> Bool) -> [a] -> [a]
bar f xs = filter g xs
where g :: a -> Bool
g = not . f
This might look fine at first glance, but the type signature for g
is actually a lie, because without ScopedTypeVariables
it means this:
bar :: forall a. (a -> Bool) -> [a] -> [a]
bar f xs = filter g xs
where g :: forall b. b -> Bool
g = not . f
It's claiming that g
can take something of any type at all and turn it into a Bool
, when in fact g
uses the function f
. f
doesn't work on any type at all, only the particular type that bar
was called with. bar
can take a function f
of any type (the caller of bar
can choose to instantiate a
as any type), but g
is operating "inside one particular call to bar
", when we've already been passed one particular f
.
To write this type correctly we need (using ScopedTypeVariables
) this:
bar :: forall a. (a -> Bool) -> [a] -> [a]
bar f xs = filter g xs
where g :: a -> Bool
g = not . f
There's no error because your inner function does not reference any type variable from the outer one. It uses n
but its type is plain Int
, not a polymorphic one
The "rigid type variables" error happens when we make n
's type polymorphic:
foo :: Num a => a -> [a] -> [a]
foo n list = snd $ foldl f (1,[]) list where
f :: Num a => (a,[a]) -> a -> (a,[a])
f (k,l) r
| k==n = (1,l++[r])
| otherwise = (k+1,l)
Since all type signatures have an implicit forall
added in front of them, so does the one for f
. And each forall
defines its own scope.
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