我正在尝试编译一个非常简单的haskell程序。我不知道如何在Haskell中对语句(表达式)进行排序,或者没有办法实现它。
我基本上想要一个表格的功能:
f = <do stuff 1>
<do stuff 2>
...
以下应该模拟
的模式<do stuff>
<tail recursion>
但未能编译:
go 0 = 0
go i = case i of
1 -> 2
_ -> 3
go (i-1)
这也不起作用(更简单,没有递归):
go i = 1
2
上面的代码编译,但在运行时我得到了神秘的内容:
No instance for (Num (a0 -> t0))
arising from a use of `go'
Possible fix: add an instance declaration for (Num (a0 -> t0))
In the expression: go 2
In an equation for `it': it = go 2
我有肛门问题吗?没有正确排序的问题?或者没有排序功能?如果没有排序,你会如何直截了当地做
<do stuff>
<tail recursion>
感谢您的回复。
很难相信Haskell中的函数基本上只限于一个“表达式”或“语句”或者你想要的任何东西。你会如何解决以下设计的玩具示例(用erlang编写,但很容易直接翻译成prolog或其他一系列langs):
count([], X) -> X;
count([_|B], X) ->
Y = X+1,
count(B, Y).
示例运行:
erlang> count([a,b,c,d,e],0).
5
第二个句子符合我之前描述的模式:
<do stuff>
<tail recursion>
我想这个特定的例子映射到Haskell“let ... in”这里描述的人。但是如果需要10个“做东西”,或20个或更多呢?这是“让......进入”保证是尾递归(上面的例子保证是在erlang,prolog,scheme等)。
有没有办法可以简单地“拼凑”或“排序”一堆语句或表达式?在prolog或erlang中,测序由逗号完成(参见上面的玩具示例)。在c / c ++中,它由分号完成。我认为Haskell运算符只是空格,但也许它只是用这种语言完成的?
答案 0 :(得分:21)
在Haskell中,你不会编写“做东西”的函数。您编写计算值的函数。从这个角度来看,以你建议的方式构建一个函数是没有意义的:
f = <do stuff 1>
<do stuff 2>
相反,你要写一个实际的,math-y 函数,这是一个依赖于某些输入的单表达式:
f x y = blah x blah y blah
将其分解为子表达式可能很方便
f x y = res1 + res2
where res1 = x * 3
res2 = y + 4
但最后,它只是一个表达式。
用do
表示法编写的单义代码可以按顺序显示“做东西”:
f = do
thing1
thing2
但这只是一种幻觉。它也是一个表达式:
f = thing1 >> thing2
此表达式指出f
是thing1
和thing2
的组合值。现在,IO代码可以编译来代码“按顺序执行”,但这并不是它在Haskell中的含义。一个人不仅仅是在Haskell中“做事”。相反,你撰写。
答案 1 :(得分:5)
非常好的是,您能够通过“做事”提供一个Erlang代码示例。到目前为止,它有点含糊不清。只要讨论只涉及严格的语言,那么在这一点上可能会有点模糊,因为规则是相当统一和理解的。
另一方面,我们讨论的是Haskell,一种具有“懒惰”评价的语言,因此对于“做什么”意味着什么是非常具体的,否则由于错误的假设可能会产生混淆。
这是你在Haskell中提供的Erlang函数的一个相当直接的翻译:
count ([],x) = x
count (_:b, x) =
let y = x + 1
in count (b, y)
正如您所看到的,它在结构上与Erlang版本完全相同。因此,您可能想知道什么是重要的?如果Haskell中的“做东西”很容易,那为什么每个人都觉得这么复杂呢?
解释什么是大问题的一种方法是指出上面的Haskell函数等同于这个:
count ([],x) = x
count (_:b, x) = count (b, x + 1)
还有这个:
count ([],x) = x
count (_:b, x) = count (b, y) where y = x + 1
这是因为Haskell计算表达式的顺序不依赖于这些表达式的词法顺序。一般来说,Haskell会将表达式评估推迟到最后一刻。
现在,当你考虑将这种懒惰评估方案与副作用(读取或写入文件,将输出发送到屏幕等)组合起来时,它会变得有点模糊。副作用发生的顺序通常非常重要。幸运的是,Haskell中处理副作用的函数通常不会自己执行副作用。相反,它们会产生一个“动作”值,描述副作用。然后还有其他函数可以以尊重顺序的方式组成这些“动作”值。那么你只需要一些能够实际执行“动作”值所描述的副作用的东西......
答案 2 :(得分:2)
基本上,没有办法做到这一点。 Haskell是一种纯函数式语言,既没有变化也没有语句顺序。模拟序列的一种方法是 monads 。互联网上有很多教程,我个人建议你阅读Learn You A Haskell For Great Good!或Real World Haskell的monad章节。另一件事是:顺序操作在Haskell中不是很惯用。你应该从不同的东西开始。
答案 3 :(得分:1)
当你说“做东西”时,你的意思是什么?
在第一种情况下,请使用let
... in
:
quadraticRoots a b c = let discriminant = b * b - 4 * a * c
root = sqrt discriminant
in case compare discriminant 0 of
LT -> []
EQ -> [-b / (2 * a)]
GT -> [(-b + root) / (2 * a), (-b - root) / (2 * a)]
在第二种情况下,使用do
表示法:
main = do args <- getArgs
case map read args of
[a, b, c] -> putStrLn $ case quadraticRoots a b c of
[] -> "no real roots"
[x] -> "single root: " ++ show x
[x1, x2] -> unwords ["two real roots:", show x1,
"and", show x2]
_ -> hPutStrLn stderr "Please provide three coefficients on the command line"
答案 4 :(得分:1)
更新:请注意,您不是在Haskell中编写Scheme程序。在惯用的Haskell中,累积参数很少见,它使用惰性而不是尾递归。
例如,Haskell中count
的定义类似于
count [] = 0
count (x:xs) = 1 + count xs
您可能会反对source of length
看起来更像是Scheme-ish
-- | /O(n)/. 'length' returns the length of a finite list as an 'Int'.
-- It is an instance of the more general 'Data.List.genericLength',
-- the result type of which may be any kind of number.
length :: [a] -> Int
length l = len l 0#
where
len :: [a] -> Int# -> Int
len [] a# = I# a#
len (_:xs) a# = len xs (a# +# 1#)
但这是低级别的非惯用代码。引用的genericLength
与count
具有相同的结构,与length
相比具有更广泛的适用性。
genericLength :: (Num i) => [b] -> i
genericLength [] = 0
genericLength (_:l) = 1 + genericLength l
“在Haskell中执行此操作然后执行此操作”表示为纯代码中的函数组合。例如,要计算最长子列表的长度,我们将计算子列表的长度,然后计算这些长度的最大长度。在Haskell中,表达了
Prelude> :t maximum . map length
maximum . map length :: [[a]] -> Int
或
Prelude> :m + Data.List
Prelude Data.List> :t maximum . map genericLength
maximum . map genericLength :: (Ord c, Num c) => [[b]] -> c
注意:懒惰会增加细微差别,但一般意义上说。
即使在IO
内的“命令式”代码中,例如
main :: IO ()
main = do
putStr "Hello "
purStrLn "world!"
是封面下的函数组合,因为它具有与
相同的语义main :: IO ()
main = putStr "Hello " >>= \_ -> putStrLn "world!"
也许使用列表monad的示例会更清楚。假设我们想要所有毕达哥拉斯三元组(a,b,c)
,使得没有任何组件大于某个最大值 n 。
import Control.Monad (guard)
triples :: Integer -> [(Integer,Integer,Integer)]
triples n = do
a <- [1 .. n]
b <- [a .. n]
c <- [b .. n]
guard $ a*a + b*b == c*c
return (a,b,c)
如您所见,我们必须首先选择a
,然后选择b
的候选值,依此类推。
编译器将此代码转换为显式使用monadic绑定组合器>>=
。
-- indented to show nested scopes
triples_bind :: Integer -> [(Integer,Integer,Integer)]
triples_bind n =
[1 .. n] >>= \a ->
[a .. n] >>= \b ->
[b .. n] >>= \c ->
(guard $ a*a + b*b == c*c) >>= \_ ->
return (a,b,c)
在列表monad中,>>=
为concatMap
,因此上述内容与
triples_stacked :: Integer -> [(Integer,Integer,Integer)]
triples_stacked n =
concatMap (\a ->
concatMap (\b ->
concatMap (\c ->
concatMap (\_ ->
[(a,b,c)])
(guard $ a*a + b*b == c*c))
[b .. n])
[a .. n])
[1 .. n]
缩进显示结构并且令人愉悦,因为它给出了将λ与>>=
结合起来的Haskell徽标的印象。
另一种表达相同语义的方法是
triples_cm n = concatMap step2 [1 .. n] -- step 1
where step2 a = concatMap step3 [a .. n]
where step3 b = concatMap step4 [b .. n]
where step4 c = concatMap step5 (guard $ a*a + b*b == c*c)
where step5 _ = [(a,b,c)]
Haskell的模式匹配已在后台进行case
匹配。而不是
go 0 = 0
go i = case i of
1 -> 2
_ -> 3
go (i-1)
完成你开始的模式。
go 0 = 0
go 1 = go (2 - 1)
go _ = go (3 - 1)
请记住,Haskell中的变量是 immutable :它们没有C或Java中的破坏性更新。你得到一个定义变量值的镜头,然后你就会坚持下去。您可以改为将go
定义为
go 0 = 0
go x = let i = case x of 1 -> 2
_ -> 3
in go (i - 1)
或
go 0 = 0
go x | x == 1 = go (2 - 1)
| otherwise = go (3 - 1)
上面的示例都是类型检查但有同样的大问题,即go
仅在其参数为零时终止。您没有提供足够的信息来帮助解决该问题。告诉我们您正在尝试做什么,我们可以针对您的情况提供具体建议。
答案 5 :(得分:0)
我不确定你的功能必须做什么。可能你需要这样的东西:
go :: Int -> Int
go 0 = 0
go i = go (i-1)
如果该函数采用参数,例如'3'计算得3 - &gt;去2 - &gt;去1 - &gt;去0 - &gt; 0并得到答案'0'
现在,如果你想要一些排序,你可以使用关键字'do',但我相信它现在是你需要的。如果您正在尝试创建某种“变量”,请查看“let”&amp; 'in'陈述:
someInput = 7
gogogo 0 = 0
gogogo n =
let
gavk = n + 1
kravk = gavk * 2
out = kravk + 1
in
out
main =
do
putStrLn "this is sample do statement"
putStrLn "gogogo:"
putStrLn $ gogogo someInput
UPD。编辑的错误类型
答案 6 :(得分:0)
在数学上,函数由其从输入到输出的唯一映射定义。通过将输出转发到新函数来创建序列。因此,在Haskell中执行序列的自然方法是函数合成 ... f2 . f1
。有时,一个函数返回的值比下一步所需的要多。因此,您需要let
或where
来提取要转发到下一步的内容。
转发仅指从输出到输入的。从输入到输出,它是不同变量(函数)之间的 map 。
什么都不会改变。没有副作用。
do
符号也是功能组合。您可能会认为某些( Monad )通过函数处理并且可能已更改,但是未更改。代替更改,而是构造一个修改后的副本。好吧,理想情况下,因为像IO
之类的Monad会对外界产生影响。