理解Haskell声明中的多个类型/类型类

时间:2013-08-14 21:38:06

标签: haskell typeclass type-declaration

我正在尝试用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是我试图将这些放在一起的hacky尝试。

除了这有点不起作用之外,我在加载模块时遇到问题,我不确定原因:

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

我必须严重误解类型与TypeClasses,但这正是我认为

后续可能是:函数类型声明中有多个TypeClasses的语法是什么?类似的东西:

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 -> 6 -> 1 -> 3 -> 6...,因此mu为1(您必须移入一个元素才能点击序列的开头)并且lam是3(序列每三个条目重复一次)。

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

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

2 个答案:

答案 0 :(得分:5)

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

=>之前的东西不是类型而是类型类。 SomeTypeClass a => 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。如果你想确保某些类型是多个TypeClasses的成员,你会怎么做?

您可以执行类似

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

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

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

然后你只需将其他参数写成特定类型。你可以做到

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

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

答案 1 :(得分:1)

(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&GT; :k也许是也许:: * - &gt; *

基本上意味着:Maybe充当类型级函数。它有一种函数,它接受一个类型(*)并吐出另一个类型(*),因此,由于Int是一个类型,Maybe Int也是一种类型。

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

  

GHCI&GT; :k( - &gt;)
  ( - &gt;):: * - &gt; * - &gt; *
  GHCI&GT; :k(,)
  (,):: * - &gt; * - &gt; *
  GHCI&GT; :k Eq
  Eq :: * - &gt;约束