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

时间:2014-02-09 11:23:38

标签: 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

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

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

或者也许:

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

这可能吗?

编辑以进一步澄清:

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

编辑以响应@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的类型签名,并且不会污染任何名称空间。

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

它比我想要的更笨拙,但我想即使没有语言的特定语法支持,我们也可以做到这一点。

@Rhymoid,如果您也喜欢,请将其添加到您的答案中。 :)

6 个答案:

答案 0 :(得分:36)

对于自我推销感到抱歉,但正是这个特色是博士最近的一篇论文的主题。学生Thomas Winant,我自己,Frank Piessens和Tom Schrijvers,最近由Thomas在2014年PADL研讨会上发表。有关完整的论文,请参阅here。这是一种已经存在于其他语言中的功能,但与Haskell GADT等功能的交互使其足够有趣,可以解决细节问题。

Thomas正致力于GHC的实施。自撰写论文以来,它已得到进一步改进,但在GHC中实施“通配符约束”在技术上比我们预期的要困难一些。我们希望能够进一步开展工作并与GHC开发人员联系以使其获得通过,但这是否发生可能取决于人们希望在Haskell中拥有该功能的人数......

更新14-4-2015:经过Thomas的大量工作以及SPJ和其他GHC人员的意见,GHC 7.10中已发布部分类型签名。 Thomas Winant写了an introductory blog post关于如何使用它们的信息。

答案 1 :(得分:13)

我一直在寻找一种方法来说'x的类型与T'统一。 Will Ness和chi给出的解决方案与我提出的解决方案很接近,但是有一种方法可以在Haskell 98中完成,而无需屠宰自己的功能。

-- 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)

答案 2 :(得分:10)

你正在寻找我们很多人都想要的功能,但是Haskell没有。也不是。你想要一种部分类型的签名。建议的符号是

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

_表示"这里有一种类型,但我不愿意将其写出来#34;。

它看起来像是一个非常容易实现的功能(在签名中使用统一变量实例化_),但是没有人愿意计算出语义细节以及与其他功能的交互。

答案 3 :(得分:6)

要仅指定参数的类型,可以编写类似

的内容
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

并相应地改变其推断类型:

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

您可以使用相同的扭曲技术来指定函数的返回类型,可能以样式定义,但这并不漂亮。

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

它与您现在拥有的内容没有太大差别,只需将代码和类型规范分开。

答案 4 :(得分:2)

如果你的fn类型可以在没有签名的情况下自动推断,并且你只希望编译器检查推断类型是否是正确的形式,你可以使用以下内容。

想法是写一些诸如

之类的东西
fnSig :: exists _1 _2. forall a. _1 a -> _2
fnSig = fn

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

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

或者,可以声明一种特殊的存在数据类型。

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

应该强制编译器执行相同的类型检查。

答案 5 :(得分:1)

我认为你希望有一件非常糟糕的事情。您想要的功能稍微增加了类型推断的便利性,特别是对于顶级功能。但顶级声明的签名代表了必要的设计合同。它们是API,它们是文档,它们是陌生人搜索代码的信标,因此它们必须坚如磐石而且清晰。

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

 f (x :: Int) = 2*x
带有XScopedTypeVariables扩展的

语法(但它不适用于无点函数)。