Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Misconception on Type Classes and variable assignment in Haskell [duplicate]

Very new to Haskell and trying to understand how type classes and variables interact.

My first thing to play with was:

i :: a; i = 1

My expectation was that, since i was typed as generically as possible, I should be able to assign absolutely anything to it. (I know that I probably can't do anything with variable i, but that wasn't important.)

But, I was wrong. The above gives an error and requires that it be:

i :: Num a => a; i = 1

After playing around a bit more I came up with the following:

g :: Num a => a -> a; g a = a + 1
g 1
(returned 2)
gg :: Num a => a; gg = g 1
gg
(returned 2)

Ok... so far so good. Let's try a Fractional parameter.

g :: Num a => a -> a; g a = a + 1
g 1.3
(returned 2.3)
gg :: Num a => a; gg = g 1.3
(error)

So, please... what is it about variables that causes this? From a non-functional programming background, it "looks" like I have a function that returns a value with a type implementing Num and tried to assign it to a variable with a type implementing Num. Yet, the assignment fails.

I'm sure this is some basic misconception I have. It's probably the same thing that prevents the first example from working. I really want to get it straightened out before I start making far more serious conceptual errors.

like image 954
Marc L. Allen Avatar asked Jan 02 '23 00:01

Marc L. Allen


1 Answers

i :: a; i = 1

My expectation was that, since i was typed as generically as possible, I should be able to assign absolutely anything to it. (I know that I probably can't do anything with variable i, but that wasn't important.)

No, it's the other way around. The type represents how that value can be used later on, i.e. it states that the user can use i pretending that it is of any type that might be required at that time. Essentially, the user chooses what the type a actually is, and the code defining i :: a must conform to any such choice of the user.

(By the way we usually call i = 1 "binding" or "definition", not "assignment" since that would imply we can reassign later on.)

gg :: Num a => a; gg = g 1.3
(error)

The same principle applies here. gg claims to be of any numeric type the user might want, but if the user later on chooses, say, Int the definition g 1.3 does not fit Int.

The user can choose the type using an explicit signature (print (gg :: Int)), or putting it into context that "forces" the type (print (length "hello" + gg) forces Int since length returns Int).

If you are familiar with "generics" in some other languages, you can draw a comparison with this code:

-- Haskell
i :: a
i = 1            -- type error

-- pseudo-Java
<A> A getI() {
  return 1;      -- type error
}

From a more theoretical perspective, you are thinking of the wrong quantifier. When you write i :: a, you are thinking i :: exists a . a (not a real Haskell type) which reads as "i is a value of some type (chosen at definition time)". Instead in Haskell i :: a means i :: forall a . a which reads as "i is a value of all types (any type that might be needed on use)". Hence it boils down to "exists" vs "forall", or to "who chooses what type type a actually is".

like image 151
chi Avatar answered May 16 '23 06:05

chi