我正在尝试用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
更改为Int
或Num
都不会让它变得更好。
我不理解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
构造对我来说似乎没有用。它是一种计算重复调用次数的方法。我不确定是否有更好/不同的方式,更不像保存变量的状态。
答案 0 :(得分:5)
您不能说Integer a
或Int a
。你可能意味着Integral a
。 Integral
包含所有类型为整数的类型,包括Integer
和Int
。
=>
之前的东西不是类型而是类型类。 SomeTypeClass a => a
表示“类型为a
的成员的任何类型SomeTypeClass
”。
你可以这样做:
function :: Int -> String
这是一个接受Int
并返回String
的函数。你也可以这样做:
function :: Integer -> String
这是一个接受Integer
并返回String
的函数。你也可以这样做:
function :: Integral i => i -> String
这是一个采用Int
或Integer
或任何其他类似整数类型的函数,并返回String
。
关于你的第二个问题,你的猜测是正确的。你好吗
mu :: (Eq a, Integral b) => (a -> a) -> a -> a -> b -> (b, a)
您的评论问题:
您可以执行类似
的操作function :: (Show a, Integral a) => a -> String
这会将a
限制为同时属于Show
和Integral
的任何类型。
然后你只需将其他参数写成特定类型。你可以做到
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;约束