起点:
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填写我的函数的未指定(或不完全受限)类型的空白。
是的,像这样......
fn list = 2 * length list
where
_ = list :: [Char]
...可以工作,但仅适用于参数,并且仅当函数不是无点时。有没有办法将此技术应用于无点函数或返回值?
我受到了启发,并且使用了@ 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,如果您也喜欢,请将其添加到您的答案中。 :)
答案 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
您可以使用相同的扭曲技术来指定函数的返回类型,可能以pointfree样式定义,但这并不漂亮。
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扩展的语法(但它不适用于无点函数)。