【问题标题】:How to define function signatures partially in Haskell?如何在 Haskell 中部分定义函数签名?
【发布时间】:2014-02-09 11:23:37
【问题描述】:

起点:

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

假设我们想要约束返回值,那么我们可以这样写:

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

限制 only 参数怎么样?很简单。

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,如果您也喜欢它,请将其添加到您的答案中。 :)

【问题讨论】:

  • Erm... 你可以在 Haskell 中很好地做到这一点,当然只有你需要合适的类型类约束和多态函数,否则它无法工作。但是考虑fromInteger :: Num a => Integer -> a
  • 这只是在完全没有类型签名和必须给出一个精确的签名之间有一个中途的房子。您可以按照此问题中描述的方式在 F# 中使用 _。
  • @GaneshSittampalam 这正是我要找的! “在完全没有类型签名和必须给出精确的签名之间的中途之家”。
  • 我看不出使用类型变量a 有什么问题——它不限制任何类型,是吗?或者这只是显示不受限制的意图?
  • @Bergi fn :: [Char] -> a ; fn list = 2 * length list results in compile error "无法将预期类型 a 与实际类型 Int 匹配 -- a 是受类型签名约束的刚性类型变量for fn"——意思是,因为我们声明了fn :: [Char] -> a,所以定义不能产生比user-given类型签名更窄的类型。编译器假设如果用户说a(即任何类型),他们是认真的。

标签: haskell


【解决方案1】:

对不起,自我推销,但这个功能正是博士最近一篇论文的主题。学生 Thomas Winant、我自己、Frank Piessens 和 Tom Schrijvers,最近由 Thomas 在 PADL 2014 研讨会上介绍。全文见here。这是一个已经存在于其他一些语言中的功能,但是与 Haskell GADT 等功能的交互使得它足够有趣,可以计算出细节。

Thomas 正在着手实施 GHC。自论文撰写以来,它已经进一步改进,但在 GHC 中实现“通配符约束”在技术上比我们预期的要困难一些。我们希望能够进一步研究它并联系 GHC 开发人员以使其采用,但是否会发生这种情况可能取决于有多少人希望在 Haskell 中拥有该功能......

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

【讨论】:

  • 你在论文中说你“与 GHC 对本地绑定的非泛化一致”——这是否意味着如果你使用 NoMonoLocalBinds 设置来允许这些绑定,部分类型签名将不起作用无论如何都要概括?
  • 好问题。我们还没有研究过这种情况,但我希望在这种情况下,我们应该将设置应用于部分签名。无论如何,目前的计划是先看看 GHC 开发人员是否对该功能有足够的兴趣,然后再努力研究这类问题。 (可能)与 ScopedTypeVariables、RankNTypes、ConstraintKinds 等其他功能的交互还有待研究,例如...
  • 不是真正的 GHC 开发人员,但如果添加它,我肯定会对使用它感兴趣。
  • 将另一个答案标记为已接受,因为它包含一个现在可用的解决方法。如果我可以推动实现这一目标,我会两次投票支持你的努力。期待将此功能合并到上游。发生这种情况时,我可能会相应地更改接受的答案——因为不再需要解决方法。
  • @Wizek:我们希望如此,是的。我们正在准备一些技术资料和讨论点,并将尽快联系 ghc-devs 邮件列表..
【解决方案2】:

我一直在寻找一种方式来表示“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)

【讨论】:

  • 越来越多的创意解决方案不断涌现。好的。或者称它们为解决方法更正确,但我确实很欣赏他们的创造力。 :) 我还没想过这个。顺便说一句,写_ = fn `asTypeOf` fnTy而不是fn_unifies_with_type = let _ = fn `asTypeOf` fnTy in ()不是有意义吗?
  • 这也有效,而且要短得多:) 你甚至可以选择_ = fn `asTypeOf` (undefined :: [Char] -> a)
  • 更进一步:fn = (2 *) . length where _ = fn `asTypeOf` (undefined :: [Char] -> a) 然后它就真正成为签名的一部分。 (如果它在 where 之外,那么编译器会抱怨不匹配,但 :t fn 显示 fn :: [a] -> Int
  • 什么意思?是什么使它不那么可读?如果它有适当的换行和缩进,你还会认为它不那么可读吗?另外,只是为了通知:我对我的问题进行了编辑以回应您的回答。
  • 我喜欢这个_ = fn `asTypeOf` (undefined :: [Char] -> a)。看起来很整洁。
【解决方案3】:

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

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

_ 的意思是“这里有一个类型,但我懒得写出来”。

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

【讨论】:

  • 我明白了。如果我们很多人都喜欢它(正如@GaneshSittampalam 所指出的,它甚至可以在 F# 中使用)并且很容易实现,为什么还没有添加它?或者更好的是,我们如何添加它?
  • 我认为最近他们添加了类似于 agda 的“孔”?
  • @SassaNF 你的意思是最近添加了一个功能来回答我的主要问题?您能否提供一个链接,以便我阅读它?
  • 我认为漏洞在表达式级别——你在表达式中写_foo,编译器会告诉你它应该有什么类型:haskell.org/haskellwiki/GHC/TypeHoles
  • @Wizek SPJ 说,如果有人向他展示打字规则是什么(并且看起来很合理),他会添加它。
【解决方案4】:

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

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

它也与您现在拥有的没有太大区别,只是将代码和类型规范分开。

【讨论】:

  • 感谢发帖!我根据您的回答编辑了我的主要问题。
  • 谢谢asTypeOf,不知道。
【解决方案5】:

如果您的 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

上面的“技巧”本质上与例如中使用的相同。运行ST。

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

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

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

【讨论】:

    【解决方案6】:

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

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

     f (x :: Int) = 2*x
    

    带有 XScopedTypeVariables 扩展的语法(但它不适用于无点函数)。

    【讨论】:

    • Haskell 还允许您完全省略这些签名。这并没有更糟。
    • @GaneshSittampalam 但它大声抱怨。这仅适用于一次性代码。
    • P.S.而且我担心扩展编写一次性代码的能力会将haskell变成一次性语言。
    • 可以为部分签名打开适用于缺少签名的相同警告。
    • 好吧,我不完全同意你关于类型签名的论点,尽管我通常在生产代码的顶级声明中使用它们。但对我来说,主要优势在于辅助定义中类型推断由于某种原因失败并且您需要提供提示 - 仅提供最小提示而没有语法开销会更好。
    猜你喜欢
    • 2018-07-25
    • 1970-01-01
    • 1970-01-01
    • 2016-09-20
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多