有没有办法用纯函数式语言调用两个函数(一个接一个)?(在非io模式下)

uin*_*r_t 3 lambda haskell functional-programming

我试图理解纯函数式语言的执行顺序.

我知道在纯函数式语言中,没有必要的执行顺序.

所以我的问题是:

假设有两个功能.我想知道我可以在一个接一个地调用一个函数的所有方法(除了从另一个函数嵌套调用一个函数)(和io-mode除外).

我想在Haskell或伪代码中看到示例.

Dav*_*vid 5

如果函数是完全独立的,并且在调用另一个函数时不使用结果,则无法执行您描述的操作.

这是因为没有理由这样做.在辅助效果自由设置中,调用函数然后忽略其结果与在调用该函数所花费的时间内不执行任何操作完全相同(将内存使用情况除去).

这是有可能的是seq x y将评估x,然后y,然后给你y作为它的结果,但这种评估顺序无法得到保证.

现在,如果我们确实有副作用,例如我们在Monad或Applicative中工作,这可能很有用,但我们并没有真正忽略结果,因为隐式传递了上下文.例如,你可以做到

main :: IO ()
main = putStrLn "Hello, " >> putStrLn "world"
Run Code Online (Sandbox Code Playgroud)

在IO Monad中.另一个例子是Monad列表(可以认为它代表了一个非确定性计算):

biggerThanTen :: Int -> Bool
biggerThanTen n = n > 10

example :: String
example = filter biggerThanTen [1..15] >> return 'a'  -- This evaluates to "aaaaa"
Run Code Online (Sandbox Code Playgroud)

请注意,即使在这里,我们也不是真的忽略了结果.我们忽略了特定的值,但是我们使用了结果的结构(在第二个例子中,结构将是结果列表来自filter biggerThanTen [1..15]5个元素的事实).

但是,我应该指出,以这种方式排序的事物不一定按照它们编写的顺序进行评估.您可以使用列表Monad示例来查看此内容.尽管有更大的例子,这变得更加明显:

example2 :: [Int]
example2 =
  [1,2,3] >>=
    (\x -> [10,100,1000] >>=
             (\y -> return (x * y)))   --  ==> [10,100,1000,20,200,2000,30,300,3000]
Run Code Online (Sandbox Code Playgroud)

这里的主要内容是评估顺序(在没有诸如IO和忽略底部的副作用的情况下)不会影响Haskell中代码的最终含义(除了可能的效率差异,但这是另一个主题).结果,从来没有理由以问题中描述的方式"一个接一个地"调用两个功能(即,呼叫彼此完全独立).

记谱法

表示法实际上完全等同于使用>>=>>(实际上涉及另一个涉及模式匹配失败的事情,但这与手头的讨论无关).编译器实际上是用do notation编写的东西,并将它们转换为>>=>>通过一个名为"desugaring"的进程(因为它删除了语法糖).以下是用符号写的上面三个例子:

IO示例

main :: IO ()
main = do
  putStrLn "Hello, "
  putStrLn "World"
Run Code Online (Sandbox Code Playgroud)

第一个列表示例

biggerThanTen :: Int -> Bool
biggerThanTen n = n > 10

example :: String -- String is a synonym for [Char], by the way
example = do 
  filter biggerThanTen [1..15]
  return 'a'
Run Code Online (Sandbox Code Playgroud)

第二个列表示例

example2 :: [Int]
example2 = do
  x <- [1,2,3]
  y <- [10,100,1000]
  return (x * y)
Run Code Online (Sandbox Code Playgroud)

以下是转换的并排比较:

do          --
  m         --  m >> n
  n         --


do          --
  x <- m    -- m >>= (\x ->
  ...       --           ...)
Run Code Online (Sandbox Code Playgroud)

理解符号的最好方法是首先理解>>=,return因为正如我所说,编译器将符号转换为符号.

作为旁注,>>就是一样>>=,它只是忽略了它的左参数的"结果"(尽管它保留了"上下文"或"结构").所以所有的定义>>必须等同于m >> n = m >>= (\_ -> n).

扩展>>=第二个列表示例

为了帮助驱动Monads通常不纯的点,让我们>>=使用列表的Monad定义扩展第二个列表示例中的调用.定义是:

instance Monad [] where
  return x = [x]
  xs >>= f = concatMap f xs
Run Code Online (Sandbox Code Playgroud)

我们可以转换example2成:

第0步(我们已有的)

example2 :: [Int]
example2 =
  [1,2,3] >>=
    (\x -> [10,100,1000] >>=
             (\y -> return (x * y)))
Run Code Online (Sandbox Code Playgroud)

第1步(转换第一个>>=)

example2 =
  concatMap
    (\x -> [10,100,1000] >>=
              (\y -> return (x * y)))
    [1,2,3]
Run Code Online (Sandbox Code Playgroud)

第2步

example2 =
  concatMap
    (\x -> concatMap
             (\y -> return (x * y))
             [10,100,1000])
    [1,2,3]
Run Code Online (Sandbox Code Playgroud)

第3步

example2 =
  concatMap
    (\x -> concatMap
             (\y -> [x * y])
             [10,100,1000])
    [1,2,3]
Run Code Online (Sandbox Code Playgroud)

所以,这里没有神奇的功能,只是正常的函数调用.

  • @uintptr_t:实际上,仅仅因为某些东西使用`do`,这并不一定意味着它不纯或者它使用IO.`do`符号实际上被转换为对`>> =`和`>>`的调用.我在答案中添加了一些关于符号的更多信息(我知道这与你在这里提出的要求相反,但我想指出`do`和Monads与纯度和IO的想法完全分开).对于列表,`xs >> = f = concatMap f xs`因此,例如,那里没有杂质.实际上,IO是规则的*例外*.大多数Monads专门涉及纯函数调用. (2认同)

bhe*_*ilr 4

您可以编写一个函数,其参数取决于另一个函数的计算:

-- Ads the first two elements of a list together
myFunc :: [Int] -> Int
myFunc xs = (head xs) + (head $ tail xs)
Run Code Online (Sandbox Code Playgroud)

如果这就是你的意思。myFunc xs在这种情况下,如果不计算head xshead $ tail xs和 ,则无法获得 的输出(+)。这里有一个命令。但是,编译器可以选择执行哪个顺序head xshead $ tail xsin,因为它们彼此不依赖,但它无法在没有其他结果的情况下进行加法。它甚至可以选择并行或在不同的机器上评估它们。要点是纯函数,因为它们没有副作用,所以不必按照给定的顺序进行计算,直到它们的结果相互依赖。


查看上述函数的另一种方式是用图表表示:

                            myFunc
                              |
                             (+)
                            /   \
                           /     \
                         head    head
                           \      |
                            \    tail
                             \  /
                              xs
Run Code Online (Sandbox Code Playgroud)

为了评估一个节点,必须首先评估它下面的所有节点,但可以并行评估不同的分支。首先xs必须评估,至少部分评估,但之后可以并行评估两个分支。由于惰性求值存在一些细微差别,但这本质上就是编译器构建求值树的方式。


如果您确实想强制一个函数先于另一个函数调用,则可以使用该seq函数。它需要两个参数,强制评估第一个参数,然后返回第二个参数,例如

myFunc2 :: [Int] -> Int
myFunc2 xs = hxs + (hxs `seq` (head $ tail xs))
    where hxs = head xs
Run Code Online (Sandbox Code Playgroud)

这将强制head xs评估 before head $ tail xs,但这比排序功能更严格。

  • 你可以使用 seq 函数,强制在计算第二个参数之前计算第一个参数,但通常这种计算在 Haskell 中是没有意义的。由于没有副作用,因此先评估一个再评估另一个(如果它们是独立的)并不重要。 (2认同)
  • 让我补充一些挑剔的地方。当使用非严格函数时,顺序图的思想很难应用。例如,将 `fx ++ gx` 减少为 WHNF 不会导致调用 `gx`,除非 `fx` 是空列表。另外,与直觉相反的是,“seq xy”不需要在“y”之前评估“x”,而只需要在“x”为底部时评估到底部:实现可以首先评估“y”,然后`x`,然后返回`y`的结果。相反,“pseq x y”确实保证首先评估“x”,因为我们确实需要它来强制并行评估。 (2认同)