如何在Haskell中部分定义函数签名?

初始点:

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

假设我们只想限制返回值,那么我们可以这样写:

 fn list = (2 * length list) :: Int 

如何限制只有论据? 简单。

 fn list = 2 * length (list :: [Char]) 

虽然这是有效的,但最好将顶部的签名收集起来,而不是散布在函数体的周围。

这是最接近我可以来这个:

 fnSig = undefined :: [Char] -> a fn | False = fnSig | True = (* 2) . length 

基于http://okmij.org/ftp/Haskell/partial-signatures.lhs通过http://okmij.org/ftp/Haskell/types.html#partial-sigs

不过,我想要一个更清洁的解决scheme。 更好的沟通,我的意图是部分的限制。 像这样的东西,例如:

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

或者可能:

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

这可能吗?

编辑进一步说明:

@GaneshSittampalam在下面的评论中提到了一个重要的观点。 我正在寻找“没有任何types签名的中间房屋,并且必须给出一个确切的房子”。 所以,我不是在寻找一个基于TypeClass的答案,我只是希望GHC填写我的函数的未指定(或不完全限制)types的空白。

编辑回应@WillNess

是的,这样的事情…

 fn list = 2 * length list where _ = list :: [Char] 

…可以工作,但只能用于参数,并且只有在函数不是无点的情况下。 有没有办法将这种技术应用于无点函数或返回值?

编辑回复@Rhymoid

我得到了启发,并用@ Rhymoid的想法玩弄了这个想法:

 fn = (2 *) . length where _ = fn `asTypeOf` (undefined :: [Char] -> a) _ = fn `asTypeOf` (undefined :: a -> Int) _ = fn `asTypeOf` (undefined :: a -> b) _ = fn `asTypeOf` (undefined :: a) 

这种方法也限制了fn的types签名,并且不会污染任何名字空间。

通常我们只有一个asTypeOf行,我只是添加了多个来展示这种方法是多么强大。

这比我想要的要笨多了一点,但是我觉得这很简单,即使没有语言的特定语法支持,我们也可以做到这一点。

@Rhymoid,如果你也喜欢,请把它添加到你的答案。 🙂

对于自我推销感到抱歉,但这个特点正是博士最近的一篇论文的主题。 学生Thomas Winant,我自己,Frank Piessens和Tom Schrijvers,最近由Thomas在2014年PADL研讨会上发表。 看到这里的全文。 这是一个已经存在于其他语言中的function,但是与Haskell GADTs等function的交互使得它足够有趣,能够解决细节问题。

Thomas正在为GHC进行实施。 自从论文写作以来,它进一步得到了改善,但是在GHC中实施“通配约束”在技术上比我们预期的要难一些。 我们希望能够进一步开展工作,并联系GHC开发人员来采纳它,但是否发生这种情况可能取决于有多less人希望在Haskell中具有此function。

更新14-4-2015:经过Thomas的大量工作以及SPJ和其他GHC人员的投入,部分types签名已在GHC 7.10中发布。 Thomas Winant写了一篇关于如何使用它们的介绍性博客文章 。

我一直在寻找一个方法来说' x的types与T '相统一。 Will Ness和chi提供的解决scheme与我所提出的解决scheme非常接近,但是在Haskell 98中有一种方法可以实现,而不会损害你自己的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 () 

你甚至可以去

 fn = (2 *) . length where _ = fn `asTypeOf` (undefined :: [Char] -> a) 

你正在寻找我们中许多人想要的function,但是Haskell没有这个function。 也不是ghc。 你想要一种部分types的签名。 为此build议的符号是

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

这里的_表示“这里有一个types,但我不能把它写出来”。

它看起来像一个非常简单的特征来实现(在签名中用统一variables实例化_ ),但是没有人打算解决语义细节和与其他特征的交互。

要只指定一个参数的types,你可以写类似的东西

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

所以后来很容易修改它,比如说,

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

并据此推断其types:

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

你可以使用相同的扭曲技术来指定一个函数的返回types,可能是用无点点样式来定义的,但这并不美观。

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

这与现在的情况没有多大区别,只是将代码和types规范分开。

如果您的fntypes可以自动推断而没有签名,并且您只希望编译器检查推断的types是否是正确的forms,那么您可以使用以下内容。

这个想法是写一些像

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

除了Haskell不允许上面的存在types。 但是,可以使用更高级别的types来模拟存在types,如下所示:

 {-# 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 

上面的“技巧”基本上和runST中使用的那个相同。

或者,可以声明一个临时存在的数据types。

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

这应该强制编译器执行相同的types检查。

我想你希望有一件很糟糕的事情。 你想要的function会稍微增加types推断的灵活性,特别是对于顶级function。 但是顶层声明的签名代表了基本的devise合同。 他们是API,他们是文档,他们是陌生人进入你的代码的信标,因此他们必须坚如磐石,清晰。

是的,haskell允许返回types的types约束。 但是这主要是为了let-blocks的临时结果。 是的,你可以使用

  f (x :: Int) = 2*x 

语法与XScopedTypeVariables扩展(但它不适用于免费function)。