Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rigid type variable -- why no error appears?

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.

like image 887
Tarek Soliman Avatar asked Dec 13 '21 20:12

Tarek Soliman


People also ask

Is there such a thing as an error variable?

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.

What is a type I error?

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.

Is it possible to change the type of a variable?

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.

What are incompatible type errors?

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.


Video Answer


3 Answers

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.

like image 51
chi Avatar answered Nov 15 '22 08:11

chi


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
like image 28
Ben Avatar answered Nov 15 '22 06:11

Ben


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.

like image 38
Will Ness Avatar answered Nov 15 '22 07:11

Will Ness