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.