How to define function signatures partially in Haskell?

后端 未结 6 1197
刺人心
刺人心 2021-02-03 17:54

Starting point:

fn :: [a] -> Int
fn = (2 *) . length

Let\'s say we only want to constrain the return value, then we could write:

相关标签:
6条回答
  • 2021-02-03 18:10

    To specify only the type of an argument, you can write something like

    fn list = 2 * length list
      where
        a :: [Char]
        a = list `asTypeOf` a
    

    So that it is easy to later amend it like, e.g.,

    fn list = 2 * fromIntegral (length list)
      where
        a :: [Char]
        a = list `asTypeOf` a
    

    and have its inferred type change accordingly:

    *Main> :t fn
    fn :: [Char] -> Int
    *Main> :r
    -- changed file reloaded
    *Main> :t fn
    fn :: (Num t) => [Char] -> t
    

    You could use the same contorted technique to specify the return type of a function, perhaps defined in pointfree style, but this is not pretty.

    fn2 list = r
      where
        r :: Int
        r = f list
        f = (2 *) . length
    

    It is also not much different from what you have right now, just keeps the code and the type spec separated.

    0 讨论(0)
  • 2021-02-03 18:12

    I've been looking for a way to say 'x's type unifies with T'. The solutions given by Will Ness and chi are close to what I came up with, but there is a way to do it in Haskell 98, without butchering your own function.

    -- Your function, without type signature.
    fn = (2 *) . length
    
    -- The type signature, without actual definition.
    fnTy :: [Char] -> a
    fnTy = undefined
    
    -- If this type checks, then the type of 'fn' can be unified 
    --                                      with the type of 'fnTy'.
    fn_unifies_with_type :: ()
    fn_unifies_with_type = let _ = fn `asTypeOf` fnTy in ()
    

    You could even go for just

    fn = (2 *) . length
      where
        _ = fn `asTypeOf` (undefined :: [Char] -> a)
    
    0 讨论(0)
  • 2021-02-03 18:15

    If the type of your fn can be automatically inferred without a signature, and you merely wish the compiler to check whether the inferred type is of the right form, you might use something along the following.

    The idea is to write something such as

    fnSig :: exists _1 _2. forall a. _1 a -> _2
    fnSig = fn
    

    except that Haskell does not allow the existential types above. However, existential types can be emulated using higher-rank types as follows:

    {-# LANGUAGE RankNTypes #-}
    fnSig :: (forall _1 _2.
                (forall a. _1 a -> _2)   -- your actual type, _'s are the unknowns
              ->r)->r
    fnSig = \k->k fn                     -- the compiler infers _1=[] , _2=Int
    
    -- fn :: [] a -> Int
    fn = (2 *) . length
    

    The above "trick" is essentially the same as the one used in e.g. runST.

    Alternatively, one could declare an ad-hoc existential data type.

    {-# LANGUAGE GADTs #-}
    data Ex where Ex :: (forall a. _1 a -> _2) -> Ex
    fnSig = Ex fn
    

    which should force the compiler to perform the same type checking.

    0 讨论(0)
  • 2021-02-03 18:16

    You're looking for feature that many of us would like, but that Haskell doesn't have. Nor ghc. You want a kind of partial type signatures. The suggested notation for this is

    fn :: [Char] -> _
    fn = (2*) . length
    

    Where the _ means "there's a type here, but I can't be bothered to write it out".

    It looks like a very easy feature to implement (instantiate _ with unification variables in the signature), but nobody has bothered to work out the semantic details and the interaction with other features.

    0 讨论(0)
  • 2021-02-03 18:21

    Sorry for the self-promotion, but exactly this feature is the topic of a recent paper by Ph.D. student Thomas Winant, myself, Frank Piessens and Tom Schrijvers, very recently presented by Thomas at the PADL 2014 symposium. See here for the full paper. It is a feature that is already present in some other languages, but the interaction with features like Haskell GADTs made it interesting enough to work out the details.

    Thomas is working on an implementation for GHC. It has further improved since the writing of the paper, but implementing the "wildcard constraint" in GHC is technically a bit harder than we expected. We expect to be able to work further on it and contact the GHC developers to get it adopted, but whether or not this happens may depend on how much people would like to have the feature in Haskell...

    Update 14-4-2015: After a lot of work by Thomas and input from SPJ and other GHC people, partial type signatures have been released in GHC 7.10. Thomas Winant wrote an introductory blog post about how you can use them.

    0 讨论(0)
  • 2021-02-03 18:24

    I think you wish for a pretty bad thing. Feature you want slightly increases handiness of type inference, especially for top-level functions. But signatures of top-level declarations represent essential design contracts. They are API, they are documentation, they are beacons for strangers foraying into your code, thus they have to be rock-solid and clear.

    Yes, haskell allows type constraints for return types. But this is mostly for temporary results in let-blocks. And yes, you may use

     f (x :: Int) = 2*x
    

    syntax with XScopedTypeVariables extension (yet it isn't applicable to point-free functions).

    0 讨论(0)
提交回复
热议问题