【问题标题】:Understanding multiple types/typeclasses in Haskell declarations了解 Haskell 声明中的多种类型/类型类
【发布时间】:2013-08-14 21:38:05
【问题描述】:

我正在尝试使用 Learn You A Haskell... 学习 Haskell,但我很不耐烦,想实现我最喜欢的算法,看看是否可以。

我正在研究用于循环检测的乌龟/野兔算法 (Floyd's algorithm)。

这是我目前的代码:

idx :: (Eq a) => (a -> a) -> a -> a -> a
idx f tortoise hare
    | (f tortoise) == (f (f hare)) = (f f hare)
    | otherwise = (idx f) (f tortoise) (f f hare)

mu :: (Eq a) => (a -> a) -> a -> a -> Integer -> (Integer, a)
mu f tortoise hare cntr
    | (f tortoise) == (f hare) = (cntr+1, f tortoise)
    | otherwise = (mu f) (f tortoise) (f hare) (cntr+1)

lam :: (Eq a) => (a -> a) -> a -> a -> Integer -> Integer
lam f tortoise hare cntr
    | tortoise == hare = cntr+1
    | otherwise = (lam f) tortoise (f hare) (cntr+1)

floyd :: (Eq a) => (a -> a) -> a -> (Integer, Integer)
floyd f x0 = 
    let z = (idx f) x0 x0 
        (y1, t) = (mu f) x0 z 0
        y2 = (lam f) t (f t) 0
    in (y1, y2)

tester :: (Integer a) => a -> a
tester a
    | a == 0 = 2
    | a == 2 = 6
    | a == 6 = 1
    | a == 1 = 3
    | a == 3 = 6
    | a == 4 = 0
    | a == 5 = 1
    | otherwise = error "Input must be between 0 and 6" 

(floyd tester) 0

这试图将逻辑分解为三个步骤。首先获取 f_idx == f_{2*idx} 的索引,然后从头开始移动以获取参数 mu(从第一个元素到循环开始的距离),然后移动直到遇到重复(循环的长度) .

floyd 函数是我将这些组合在一起的巧妙尝试。

除了这有点不起作用之外,我在加载模块时也遇到了问题,我不知道为什么:

Prelude> :load M:\papers\programming\floyds.hs
[1 of 1] Compiling Main             ( M:\papers\programming\floyds.hs, interpreted )

M:\papers\programming\floyds.hs:23:12:
    `Integer' is applied to too many type arguments
    In the type signature for `tester': tester :: Integer a => a -> a
Failed, modules loaded: none.

将所有出现的Integer 更改为IntNum 并没有变得更好。

我不理解Int 的误用。在本教程中,大多数函数的类型声明始终具有以下形式

function_name :: (Some_Type a) => <stuff involving a and possibly other types>

但是当我将(Eq a) 替换为(Num a)(Int a) 时,我收到了类似的错误(类型应用于太多参数)。

我试过reading this,但它与教程的符号不一致(例如almost every function defined in these examples)。

我一定是严重误解了 Types 与 TypeClasses,但这正是我认为我确实理解的,从而导致我在上面的代码中进行类型声明。

后续可能是:在函数类型声明中有多个 TypeClass 的语法是什么?比如:

mu :: (Eq a, Int b) => (a -> a) -> a -> a -> b -> (b, a)

(但这也给出了编译错误,说 Int 应用于太多参数)。

已添加

清理并根据答案进行更改,下面的代码似乎可以正常工作:

idx :: (Eq a) => (a -> a) -> a -> a -> a
idx f tortoise hare
    | (f tortoise) == (f (f hare)) = (f (f hare))
    | otherwise = (idx f) (f tortoise) (f (f hare))

mu :: (Eq a) => (a -> a) -> a -> a -> Integer -> (Integer, a)
mu f tortoise hare cntr
    | (f tortoise) == (f hare) = (cntr+1, (f tortoise))
    | otherwise = (mu f) (f tortoise) (f hare) (cntr+1)

lam :: (Eq a) => (a -> a) -> a -> a -> Integer -> Integer
lam f tortoise hare cntr
    | tortoise == hare = cntr+1
    | otherwise = (lam f) tortoise (f hare) (cntr+1)

floyd :: (Eq a) => (a -> a) -> a -> (Integer, Integer)
floyd f x0 = 
    let z = (idx f) x0 x0 
        (y1, t) = (mu f) x0 z 0
        y2 = (lam f) t (f t) 0
    in (y1, y2)

tester :: (Integral a) => a -> a
tester a
    | a == 0 = 2
    | a == 2 = 6
    | a == 6 = 1
    | a == 1 = 3
    | a == 3 = 6
    | a == 4 = 0
    | a == 5 = 1
    | otherwise = error "Input must be between 0 and 6" 

然后我明白了

*Main> floyd tester 2
(1,3)

并给出这个测试函数(基本上就像维基百科示例中的那个),这是有道理的。如果您开始x0 = 2,则序列为2 -&gt; 6 -&gt; 1 -&gt; 3 -&gt; 6...,因此mu 为1(您必须移入一个元素才能到达序列的开头),lam 为3(序列每三个条目重复一次)。

我想在你可能“重复”之前是否总是将第一点视为老化存在一些问题。

如果有人对此有任何建议,我将不胜感激。特别是,我的 cntr 构造对我来说似乎不起作用..这是一种计算重复调用次数的方法。我不确定是否有更好/不同的方式不像保存变量的状态。

【问题讨论】:

    标签: haskell typeclass type-declaration


    【解决方案1】:

    你不能说Integer aInt a。你的意思可能是Integral aIntegral 包含所有类型的整数类型,包括 IntegerInt

    =&gt; 之前的东西不是类型而是类型类。 SomeTypeClass a =&gt; a 表示“任何类型 a 是类型类 SomeTypeClass 的成员”。

    你可以这样做:

    function :: Int -> String
    

    这是一个接受Int 并返回String 的函数。你也可以这样做:

    function :: Integer -> String
    

    这是一个接受Integer 并返回String 的函数。你也可以这样做:

    function :: Integral i => i -> String
    

    这是一个函数,它接受 IntInteger 或任何其他类似整数的类型并返回 String


    关于你的第二个问题,你的猜测是正确的。你可以这样做

    mu :: (Eq a, Integral b) => (a -> a) -> a -> a -> b -> (b, a)
    

    您评论的问题:

    1。如果你想确保某个东西的 Type 是多个 TypeClass 的成员,你会怎么做?

    你可以这样做

    function :: (Show a, Integral a) => a -> String
    

    这会将a 限制为同时属于ShowIntegral 的任何类型。

    2。假设您只想将某些参数的类型限制在 TypeClass 中,而您希望其他参数属于特定类型?

    然后您只需将其他参数写为特定类型。你可以这样做

    function :: (Integral a) -> a -> Int -> String
    

    它采用任何类似整数的类型a,然后是Int,并返回String

    【讨论】:

    • 两个问题:(1)如果你想确保某个东西的类型是多个类型类的成员,你会怎么做? (2) 假设您只想将某些参数的 Type 限制在 TypeClass 中,而您希望其他参数为特定类型?
    • @EMS 在我的帖子中回答。
    • 在最后一个例子中,为什么Integer 不能在你有Int 的地方工作,在这种情况下,它不是就像代码一样,只是我限制为Eq 而不是 IntegralShow 等...?
    • 您不能将Integer 传递给应该是Int 的参数,不。就 Haskell 而言,这两种是完全不同的类型。您可以指定您希望函数采用Integer,但如果您愿意的话——我只是以Int 为例。我不确定你的第二个问题是什么意思。
    • 我明白了,我想我明白了。当我需要说出特定类型时,我将所有内容都切换为 Integer,然后将 tester 函数修复为使用 TypeClass Integral 作为参数。我不得不为f 的重复应用清理一些括号,现在它似乎正在工作。谢谢。
    【解决方案2】:

    (Rank-1)类型声明的一般形式是

    x :: [forall a b … . ] Cᴏɴꜱᴛʀᴀɪɴᴛ(a, b, …) => Sɪɢɴᴀᴛᴜʀᴇ(a, b, …)
    

    在哪里

    • forall a b … 将类型变量引入范围。这通常被省略,因为 Haskell98 隐式使用类型级别表达式中的所有小写符号作为类型变量。类型变量有点像函数的隐式参数:调用者可以选择要使用的特定类型,尽管它们必须遵守...
    • Cᴏɴꜱᴛʀᴀɪɴᴛ(a, b, …)。这通常是
      • 一个类型类标识符和一些类型变量(例如Integral a),这意味着“调用者必须确保我们可以使用a作为某种整数——添加其他数字等等。” ,
      • 此类类型类约束的元组,例如(Eq a, Show a),基本上意味着约束级别:所有的约束都需要被满足,调用者需要确保变量是所有需要的类型类的成员。
    • Sɪɢɴᴀᴛᴜʀᴇ(a, b, …) 通常是某种函数表达式,其中类型变量可能出现在箭头的任一侧。也可以有固定类型:就像你可以在(值级)Haskell 代码中混合文字和变量一样,你可以混合内置类型和局部类型变量。例如,

      showInParens :: Show a => a -> String
      showInParens x = "(" ++ show x ++ ")"
      

    不过,这些还不是最普遍的形式。就现代 Haskell 而言,

    • Cᴏɴꜱᴛʀᴀɪɴᴛ(a, b, …)kind Constraint 的任何类型级表达式,其中类型变量可能出现,但也可能出现任何合适的类型构造函数。
    • Sɪɢɴᴀᴛᴜʀᴇ(a, b, …) 非常类似地是任何类型级别的表达式*(实际类型的类型),其中类型变量可能出现,但也可能出现任何合适的类型构造函数。李>

    现在什么是类型构造函数?这很像值级别上的值和函数,但显然是在类型级别上。例如,

    GHCi> :k 也许
    也许 :: * -> *

    这基本上意味着:Maybe 充当类型级函数。它有一种函数,它接受一个类型(*)并吐出另一个类型(*),所以,既然Int 是一个类型,Maybe Int 也是一个类型。

    这是一个非常笼统的概念,虽然可能需要一些时间才能完全掌握它,但我认为以下内容很好地解释了可能仍然需要说明的所有内容:

    GHCi> :k (->)
    (->) :: * -> * -> *
    GHCi> :k (,)
    (,) :: * -> * -> *
    GHCi> :k 方程
    Eq :: * -> 约束

    【讨论】:

    • 哈哈。 “(Rank-1)类型声明的一般形式......” - 是的,这是一个很好的方式来吸引一个刚刚说“我正在尝试学习 Haskell”的人)
    • @NikitaVolkov:嗯,kqr 已经提供了一个简单、中肯的答案。但是即使在 JavaScript 这样的语言中,类似的东西也适用于语法问题。 Haskell 中所有这些东西的统一性是多么的好,这对 IMO 来说真的很吸引人,对于初学者来说也是如此,但只有在你真正在适当的一般环境中考虑它时才会出现。在写“尽可能通用”之类的东西时,我觉得应该暗示这仍然不是真的,RankNTypes。但对于不知道那是什么的人来说,“(Rank-1)”肯定不会太令人困惑,它也没有太大意义。
    • 当然,这通常是一个有用的答案,我赞成。但我只是想象你提出的概念会让一个新手感到多么沮丧。
    猜你喜欢
    • 1970-01-01
    • 2022-08-12
    • 1970-01-01
    • 1970-01-01
    • 2016-06-01
    • 1970-01-01
    • 2015-12-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多