简体   繁体   中英

Composing functions with a intermediate polymorphic type in Haskell

I have the following file:

module SimpleComposition where

class Intermediate a where
    f :: a -> Int
    g :: Char -> a

h :: Char -> Int
h = f . g

When trying loading it in ghci, I get the error:

main.hs:8:5: error:
    * No instance for (Intermediate a0) arising from a use of `f'
    * In the first argument of `(.)', namely `f'
      In the expression: f . g
      In an equation for `h': h = f . g
  |
8 | h = f . g
  |     ^

I believe the problem is that someone could be using 2 different types which are instances of Intermediate in this composition. How can I guarantee it is the same when I export this module?

PS: This is a better minimal example of the problem I have than the question I asked before ( How to compose polymorphic functions in Haskell? ).

The problem isn't really that the instance can't be inferred, but that the compiler really has no way of knowing what type you could possibly want there. g can produce any type that's asked from it (provided it has an Intermediate instance), f can consume any such type... but nobody specifies which one .

But that's easy to fix: just select a type now. Of course it needs to be one that does have an instance; eg if you have

instance Intermediate Char where
  f = fromEnum
  g = id

then you can use

h :: Char -> Int
h = (f :: Char -> Int) . g

A more concise way of fixing the type choice is to use a syntactic extension:

{-# LANGUAGE TypeApplications #-}

h = f @Char . g

...or, to emphasize that you're just fixing the type in the middle,

h = f . id @Char . g

I believe the problem is that someone could be using 2 different types which are instances of Intermediate in this composition.

No the problem is that Haskell can no longer derive from the signature what a to use. Imagine that there are two Intermediate types:

instance Intermediate Char where
    # …

instance Intermediate Bool where
    # …

now there are two implementations for h :

h :: Char -> Int
h = f . (g :: Char -> Char)

or:

h :: Char -> Int
h = f . (g :: Char -> Bool)

there can be an infinite number of Intermediate types that can be used. The problem is that Haskell can not tell, based on the type signature what type to use.

We can give it a type hint, but that of course means that the intermediate type is fixed.

A simple way to fix this is making use of asTypeOf:: a -> a -> a . This is basically a const function, but where the two parameters have the same type. This is thus used to add a hint what type to use, for example:

h :: Intermediate a =>  -> Char -> Int
h a x = f (g x  a)

Here the value a parameter is thus not of any importance, this is a way to "inject" the type that will be used as type for the result of g and the parameter of f .

If you thus later use h , you can work with:

h  'a'

to specify that f should have type Char -> Char , and g should have type Char -> Int .

Like @leftroundabout and @DanielWagner say, a more clean solution without making use of such dummy variable , is adding the type variable in the signature:

{-# LANGUAGE AllowAmbiguousTypes, ScopedTypeVariables, TypeApplications #-}

h :: forall a. Intermediate a => Char -> Int
h = f . g @ a

then we can use h with a type variable with:

h  'a'

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