简体   繁体   中英

do I understand this haskell code with fromIntegral correctly?

I (think) I fully understand why 6/(length [1,2,3]) does not work: length returns an int ( length :: Foldable t => ta -> Int ) and Int does not implement the / function.

One proposed solution, that works is 6/fromIntegral (length [1,2,3])

This question is about why this solution works.

My conjecture is: the function fromIntegral (see: https://hackage.haskell.org/package/numhask-0.6.0.2/docs/src/NumHask.Data.Integral.html#line-210 ) is implemented in multiple ways (non-explicitly, but still) so that it can receive many different types and return many different types. So, when compiling the code, haskell will choose amongst the possible return types and use the apropriate one (in this case, double or float).

Is my conjecture correct?

Any filling of the gaps in this explanation also appreciated

Your intuition is mostly correct.

Like any function which is polymorphic on the return type, fromIntegral will produce a value in whatever type is needed for the program to type check. Roughly speaking, it produces the type which is required by the context.

For instance, fromIntegral someValue + (3 :: Double) will make fromIntegral produce a Double , since that's what's needed for everything to type-check.

If, after type inference, Haskell still has no idea about which specific type to use, it usually raises an error, complaining about the ambiguity. As a major exception, when a numeric type is needed, instead of raising an error some "default" types are used. This exception is what makes print 3 work without having to write print (3 :: Integer) or something similar.

fromIntegral has type (Integral a, Num b) => a -> b , so you can get any type that implements Num that you need.

(/) has type Fractional a => a -> a -> a , and since Fractional is a subclass of Num , fromIntegral will be able to provide an appropriate type.

The only remaining point is what type 6 / ... will actually return; the expression itself has type Fractional a => a . That will depend on the context in which the expression is validated. In GHCi, for example, it will simply default to Double , I believe.

fromIntegral has type forall a b. (Integral a, Num b) => a -> b forall a b. (Integral a, Num b) => a -> b . If you're familiar with other languages, forall a b. is like <A, B> (Java, C#) or template<typename A, typename B> (C++): it introduces a scope for type variables for a generic function.

Normally the forall is left implicit, but you can display it with eg :set -fprint-explicit-foralls in GHCi and enable it in your code with the ExplicitForAll extension. (Note that -fprint-explicit-foralls will put curly braces around the type variables, which is not valid syntax; I'll explain below.)

The type parameters a and b (as well as the constraints Integral a and Num b ) are arguments passed in to the function by the caller of fromIntegral , but unlike value-level arguments, these type-level arguments are passed implicitly by the compiler at compile-time. When you write something like this:

6.0 / fromIntegral (length [1, 2, 3]) :: Double

fromIntegral is inferred as having the type arguments Int (because length returns an Int ) and Double (because of the type annotation). So the type variables are filled in like so:

(Integral Int, Num Double) => Int -> Double

Then the compiler finds the definitions of instance Integral Int and instance Num Double and uses their implementations of toInteger :: forall a. Integral a => a > Integer toInteger :: forall a. Integral a => a > Integer ) and fromInteger :: forall a. Num a => Integer -> a fromInteger :: forall a. Num a => Integer -> a (specialised to fromInteger :: Integer -> Double ) to do the conversion. And finally these call GHC primitive functions like smallInteger and doubleFromInteger that do the actual conversions.

With the TypeApplications extension you can write these type arguments explicitly:

> :set -fprint-explicit-foralls

> :t fromIntegral -- original function
fromIntegral :: forall {a} {b}. (Integral a, Num b) => a -> b

> :t fromIntegral @Int -- applied to one type argument
fromIntegral @Int :: forall {b}. Num b => Int -> b

> :t fromIntegral @Int @Double -- applied to both type arguments
fromIntegral @Int @Double :: Int -> Double

> :t fromIntegral @Int @Double 5 -- both type arguments *and* value argument
fromIntegral @Int @Double 5 :: Double

TypeApplications is the reason that -fprint-explicit-foralls sometimes prints curly braces {} around the type variables in a forall quantifier: the order of type arguments is defined by the type signature, and the curly braces indicate that there was a type signature specifying which type arguments you can fill in with TypeApplications . If there are no braces, then there was no signature, and you can't use TypeApplications because the argument order isn't specified.

It will always print braces when you use :type / :t because this command infers the type of an expression ; if you want to know the signature of a definition, you need to use :type +v / :t +v instead. For example:

> :set -XTypeApplications -fprint-explicit-foralls

> example1 x y = x

> example2 :: a -> b -> a; example2 x y = x

> :t example1 -- always prints braces
example1 :: forall {p1} {p2}. p1 -> p2 -> p1

> :t example2 -- always prints braces
example2 :: forall {a} {b}. a -> b -> a

> :t +v example1 -- definition has no signature; prints braces
example1 :: forall {p1} {p2}. p1 -> p2 -> p1

> :t +v example2 -- definition has a signature; no braces
example2 :: forall a b. a -> b -> a

You can use TypeApplications with example2 but not example1 :

> :t example2 @Int @Char
example2 @Int @Char :: Int -> Char -> Int

> :t example1 @Int @Char

<interactive>:1:1: error:
    • Cannot apply expression of type ‘p10 -> p20 -> p10’
      to a visible type argument ‘Int’
    • In the expression: example1 @Int @Char

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM