Haskell中的语句排序?

时间:2012-03-31 10:09:07

标签: haskell

我正在尝试编译一个非常简单的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运算符只是空格,但也许它只是用这种语言完成的?

7 个答案:

答案 0 :(得分:21)

Obligatory meme

在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

此表达式指出fthing1thing2的组合值。现在,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#)

但这是低级别的非惯用代码。引用的genericLengthcount具有相同的结构,与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。有时,一个函数返回的值比下一步所需的要多。因此,您需要letwhere来提取要转发到下一步的内容。

转发仅指从输出到输入的。从输入到输出,它是不同变量(函数)之间的 map

什么都不会改变。没有副作用。

do符号也是功能组合。您可能会认为某些( Monad )通过函数处理并且可能已更改,但是未更改。代替更改,而是构造一个修改后的副本。好吧,理想情况下,因为像IO之类的Monad会对外界产生影响。