今天我正在学习一些新的Haskell,当时我在ghci中尝试了一些东西。 它基本上归结为:
Prelude> let x = 6
Prelude> x
6
Prelude> let y = show x
Prelude> y
"6"
Prelude> let x = show x
Prelude> x
"\"\\\"\\\\\\\"\\\\\\\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" --(repeats)
那么ghci在作业中不能自我引用吗?我觉得它类似于C中的i = i++;
,或者试图在Scheme中引用let
(不是let*
)的先前分配。无论如何都要这样做,或者我应该使用更简单的let y = show x
?
答案 0 :(得分:9)
默认情况下,Haskell中的定义是递归。因此,您为x
所做的定义指的是同一个x
,这就是您看到的字符串很长的原因,因为x
被定义为String
这是调用show
本身的结果,所以你不断看到显示字符串的开头引号,然后那些开头的引号被转义,依此类推。
这些定义可能非常有用。例如,如果你写:
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
你可以将Fibonacci序列本身定义为无限列表。
然后你可以做take 10 fibs
之类的事情来看看前10个元素。
答案 1 :(得分:6)
确实是自我参考!它试图解决这个等式
x = show x
由于show
返回一个字符串,Haskell知道x
像"\""
那样开始,并且足以猜出第二个字符,这对于第三个字符就足够了第四次就足够了......
等等。
答案 2 :(得分:2)
是的,你可以在ghci中做一个非自我引用的任务,虽然它有点麻烦:
let x = 5
x <- return $ show x
let
是递归的,而monadic绑定则不是。
答案 3 :(得分:1)
再次:是的,Haskell在赋值时可以自我引用,否则这只会给出错误,而不是打印出难以辨认的东西,不是吗?
你在Haskell中无法做的事情,永远,是修改/ 重新分配变量的值。这完全是不可能的:如果您曾将x
定义为某个值,x
将永远保留此值。一种假设被烘焙到非常语言中,并且在编译器中用于很多优化等等。
现在你想知道为什么你可以再次写let x = ...
,不是我只是说这是不可能的吗?问题是,你在那里做的是定义一个新的变量也碰巧有名x
,但这并没有改变任何关于具有相同名称的旧变量。你可能会期待这个
Prelude> let x = 6
Prelude> let p = print x
Prelude> let x = 7
Prelude> p
获得7
,但实际上它会打印6
,因为p
仍然引用旧变量x
,而不是稍后定义的新变量n :: Int
n = 7
f :: IO ()
f = print $ replicate n "ha"
... -- much later, you've forgotten there was a global `n` up there...
g :: String -> String
g = take n
where n = 37
。
仍然困惑?也许不那么奇怪的是
f
f
将占用37个字符而不是7个字符是合理的,而对g
的任何调用仅继续重复字符串7次。毕竟,它是“n
,,其中 let
具有该值”,但除此之外别无其他。现在,where
只是do
反过来写的。特别是, let x = 6
in ( print x >> ( let y = show x
in ( print y >> ( let x = show x
in ( print x )
)
)
)
符号或GHCi提示符实际上是这样的语法糖:
let x = show x in ( print x )
每个paren都包含一个范围。可以使用外部作用域中定义的变量,但如果找到,则优先使用本地变量。因此,x = 6
可以完全独立考虑,原始的{{1}}会在此范围内被遮蔽。因此,定义可以工作的唯一方法是递归地引用自身。