【问题标题】:Can collections be "opened" inside function signature declaration?集合可以在函数签名声明中“打开”吗?
【发布时间】:2012-09-10 15:20:27
【问题描述】:

下面的类型T可以用Haskell或其他函数式语言正式定义吗?

类型T 包含函数,给定来自X 的一个集合(即一组)对象,为该集合中的每个对象分配一个数字 .

例如,假设我们有一个来自T 的函数t。它的参数arg 必须是来自X 的对象的集合(集合),例如字符串列表 ['abc', 'def', 'xyz']。它的返回值必须是一个函数r,它只接受三个可能的参数:'abc''def''xyz',并返回一个数字。在这种情况下,我特别不希望 r 接受任何字符串作为参数。

一个例子可能是“排名函数”,它给定一组对象,以某种方式为每个对象分配一个排名。它不只返回一组数字;而是返回一个函数,该函数采用原始集合的任何成员,并返回该成员的排名。

当然,如果我稍微改变一下我的要求,事情就会变得超级简单。我可以问这个函数t 只接受一个对象集合并返回一个数字集合。这差不多,但不完全相同。这样的定义需要一些额外的工作:我必须将输入集合中的对象与输出集合中的对象进行匹配。而且它也不会那么精确:返回的数字集合可能与输入对象不匹配(例如,一个数字可能太多)。

如果我所描述的约束不能表达为类型约束,我不会感到惊讶,并且应该以不同的方式强制执行。

编辑:我最初还要求定义一个类型U,其中包含将X 中的函数转换为数字的函数,并返回T 类型的函数。但我没有很好地解释这一点,它只会给我的问题增加困惑。所以最好忽略我的这部分问题。

【问题讨论】:

  • 我认为U 只是一个专门的Functor

标签: haskell types functional-programming language-comparisons


【解决方案1】:

T - 标记

好的,我想重命名您的类型以帮助理解它们。 T 将数字分配给值,并且由于我不确定容器的结构是什么(并且希望保持灵活),因此我将在一对 (x,n) 中弹出值和数字。让我们称之为标记,所以重命名TTagger,所以假设我知道你的集合类型是CollXs 的集合将具有类型Coll X,所以我需要

type CollTaggerXInt = Coll X -> Coll (X,Int)

它为你想要的函数创建了一个类型的同义词。

但是如果Int 太小而您想使用IntegerDouble 或其他数字类型怎么办?

data Num n => CollTaggerX n = CollTaggerX (Coll X -> Coll (X,n))

这意味着您可以使用任何固定类型的数字数据标记X 值。 (Num n => 是一个数据类型约束,它断言 n 必须是数字类型。)右侧的 CollTaggerX 通过将标记函数包装在轻量级构造函数中来确保类型安全。我们需要使用data 而不是type,因为我已经通过n 进行了参数化。

我倾向于将固定类型X 和集合类型Coll 替换为类型参数(如某些语言中的泛型,例如Java),以提高代码重用性:

data (Functor coll,Num n) => Tagger coll x n = Tgr (coll x -> coll (x,n))

所以现在我们坚持 collection 类型是 Functor,因此我们可以使用 fmap 将函数逐点应用于集合(在您的情况下至关重要,任何集合类型都将是 Functor 的实例) .

我对@9​​87654343@ 的T 定义最满意,但如果collxn 只有一种可能性,您可以使用CollTaggerXInt

U - 制作标签

您的U 类型用于将元素标记器转换为集合标记器。我想叫它Lifting,而不是U。如果您使用CollTaggerXInt,您可以再次使用类型同义词:

type LiftXIntToTagger = (X -> Int) -> Coll X -> Coll (X,Int)

或者如果您使用更灵活的Tagger 定义,您可以编写

data (Functor coll,Num n) => Lifter coll x n = Lifter ((x -> n) -> coll x -> coll (x,n))

但是为此创建一个类型感觉很疯狂,因为如果你有一个逐点函数,你可以使用 fmap 来提升它,它仍然适用于你的 coll 类型:

fmap :: Functor f => (a -> b) -> f a -> f b

所以我们可以将coll 用作fx 用作a(x,n) 用作b,并定义

liftT :: (Functor coll,Num n) => (x -> n) -> coll x -> coll (x,n)
liftT f = fmap tag   where 
      tag x = (x,f x)

如果你想定义你的类型,好的,但我认为liftT 可能是你的类型U 中唯一合理的函数。

排名 - U+上下文

现在我认为您的排名示例很有用,所以让我们调查一下。 rankfunction 需要检查集合的 所有 元素,所以让我们将整个集合作为它的第一个参数,即 rankfunction :: coll x -> x -> n(在上下文 (Functor coll,Num n) 内)。

liftInContext :: (Functor coll,Num n) => (coll x -> x -> n) -> coll x -> coll (x,n)
liftInContext rankfunction mycoll = liftT (rankfunction mycoll) mycoll

这里的函数(rankfunction mycoll) 将它的第一个参数——整个集合——传递给rank函数,然后使用liftT 将它应用于每个元素。这称为部分评估,对于这类事情非常方便。

【讨论】:

  • 在给定集合coll 的情况下,是否可以强制执行CollTaggerXInt 类型的函数必须返回第一个元素必须始终是coll 成员的对的限制?事实上,coll 的每个成员在返回的对中的第一个位置恰好出现一次?
  • 是的,如果您使用data 而不是type 并坚持CollTaggerXInt 的值只能使用liftTliftIncontext 生成。您可以通过将所有这些函数和类型放在一个不导出构造函数 (Tgr) 的模块中,只导出函数和类型 (Tagger) 来实现这一点。您将需要一些其他函数来将结果集合返回给您,或者为了更安全,将它们包装在您自己的 data 类型中,并使用未导出的构造函数。
  • @max:使用函数liftT 和/或liftIncontext 确保您要求的一对一限制,因为它们通过x 不变。如果您限制只使用这两个函数,则不需要花哨的类型定义,它们具有您需要的类型 U
  • 谢谢,我会阅读更多有关 Haskell 的内容,以确保我正确理解您的示例。
【解决方案2】:

编辑:鉴于问题的更新,我已经完全重写了这个答案。

暂时忽略对类型的强调并采取实用的方法,让我们开始在 Haskell 中构建此功能。首先,这是一个函数,给定一个函数、一个对象列表和一个对象,如果该对象在列表中,则将该函数应用于该对象。

u f list x = assert (x `elem` list) $ f x

(assert函数来自Control.Exception)这也可以通过一个守卫来实现:

u f list x | (x `elem` list) = f x --similar but gives a slightly different error msg

在 ghci 中运行一些示例会得到以下结果:

*Main> u (+1) [1,2,3] 1
2
*Main> u (+1) [1,2,3] 2
3
*Main> u (+1) [1,2,3] 3
4
*Main> u (+1) [1,2,3] 4
*** Exception: test.hs:3:14-19: Assertion failed

但是,这不是很安全的代码,因为没有静态保证不会以这种方式调用函数而导致崩溃。

另一种方法是使用Maybe 类型。

u' f list x = if elem x list then Just (f x) else Nothing

具有以下行为:

*Main> u' (+1) [1,2,3] 2
Just 3
*Main> u' (+1) [1,2,3] 3
Just 4
*Main> u' (+1) [1,2,3] 4
Nothing

这仍然不能静态保证不会以不正确的方式调用该函数,但它可以静态保证任何基于返回值的结果必须同时处理Just resultNothing。这也带来了Maybe 是一个单子的优点,可以将其视为可能失败的计算。所以你可以像这样轻松地链接调用

*Main> let func = u' (+1) [1,2,3]
*Main> func 1
Just 2
*Main> func 1 >>= func
Just 3
*Main> func 1 >>= func >>= func
Just 4
*Main> func 1 >>= func >>= func >>= func
Nothing

这似乎是在 Haskell 中解决此类问题的最佳实用方法。但是,问题询问是否有一个 type 专门固定这样的功能。让我们看看这些函数的类型:

u  :: Eq x => (x -> a) -> [x] -> x -> a
u' :: Eq x => (x -> a) -> [x] -> x -> Maybe a

这两种类型都不要求列表的成员资格使它们起作用与否。我能想到的将其作为一种类型工作的唯一方法是将集合本身​​视为一种类型。调用该类型col,然后我们可以这样做

u :: (col -> x) -> col -> x

但这不仅没有什么实用价值,而且也不能精确地确定这些功能。基于对的方法也没有。根据我的经验,尝试构建高度限制的类型(例如仅包含 ['abc', 'def', 'xyz'] 中的值的类型)并不是很实用。

【讨论】:

  • 如果我理解正确的话,我的意思是tMap' :: [a] -> a -> [Int]。除了这里的a 确保列表中元素的类型与非列表参数的类型相同。不过,我需要的是非列表参数实际上是列表中的元素之一。也就是说,tMap' [1,2,3] 产生的函数如果传递了参数4 就不会报错(因为4123 的类型相同)。我希望它检查它收到的参数是否实际上来自原始列表。抱歉,记号不好。
  • 让我们试着澄清一下,因为我现在不完全确定你在追求什么。正如我理解你在问什么,你想在某种意义上推广为a集合中的函数分配一个数字的函数(即f :: a -> Intf :: Num n => a -> n,如果你想更灵活/更复杂)到数字集合,(所以g :: [a] -> Intg :: Num n => [a] -> n)。这是您的意思吗?这与非集合对象的附加输入有何关系?
  • 抱歉我的问题解释不佳,我重写了它 - 也许现在更有意义?
  • 对了,我希望这次能理解你,并详细重写了我的答案:)
  • (注意(\x -> x + 1) 就是(+1),简单一点:)
【解决方案3】:

如果您想要一个接受一个值并返回一个类型(除其他外)的函数,您将需要依赖类型。你可以在 Haskell 中 simulate dependent types,但它并不漂亮。

【讨论】:

    猜你喜欢
    • 2015-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 2019-02-09
    • 1970-01-01
    • 1970-01-01
    • 2012-11-08
    相关资源
    最近更新 更多