我试图用纯粹的函数式语言来理解执行的顺序。
我知道在纯函数式语言中,没有必要的执行顺序。
所以我的问题是:
假设有两个功能。 我想知道我可以在一个接一个地调用一个函数的所有方法(除了从另一个函数嵌套调用一个函数)(和io-mode除外)。
我想在Haskell或伪代码中看到示例。
答案 0 :(得分:5)
如果函数是完全独立的,并且在调用另一个函数时不使用结果,则无法执行您描述的操作。
这是因为没有理由这样做。在副作用自由设置中,调用函数然后忽略其结果与在调用该函数所花费的时间内没有任何操作完全相同(将内存使用情况除去)。
可能 seq x y
将评估x
然后评估y
,然后为您提供y
作为结果,但此评估订单无法保证。
现在,如果我们做有副作用,例如我们在Monad或Applicative中工作,这可能很有用,但我们不是真正忽略结果因为隐式传递了上下文。例如,你可以做
main :: IO ()
main = putStrLn "Hello, " >> putStrLn "world"
在IO Monad中。另一个例子是Monad列表(可以认为它代表了一个非确定性计算):
biggerThanTen :: Int -> Bool
biggerThanTen n = n > 10
example :: String
example = filter biggerThanTen [1..15] >> return 'a' -- This evaluates to "aaaaa"
请注意,即使在这里,我们也不会真的忽略结果。我们忽略了特定的值,但是我们使用结果的结构(在第二个例子中,结构将是filter biggerThanTen [1..15]
的结果列表有5个元素的事实。)
example2 :: [Int]
example2 =
[1,2,3] >>=
(\x -> [10,100,1000] >>=
(\y -> return (x * y))) -- ==> [10,100,1000,20,200,2000,30,300,3000]
这里的主要内容是评估顺序(在没有诸如IO和忽略底部的副作用的情况下)不会影响Haskell中代码的最终含义(除了可能的效率差异,但这是另一个主题)。因此,从来没有理由以问题中描述的方式“一个接一个地”调用两个函数(即,调用彼此完全独立)。
注释实际上完全等同于使用>>=
和>>
(实际上还有一个涉及模式匹配失败的事情,但这与手头的讨论无关)。编译器实际上是用do notation编写的东西,并通过一个名为“desugaring”的过程将它们转换为>>=
和>>
(因为它删除了语法糖)。以下是用符号写的三个例子:
IO示例
main :: IO ()
main = do
putStrLn "Hello, "
putStrLn "World"
第一个列表示例
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'
第二个列表示例
example2 :: [Int]
example2 = do
x <- [1,2,3]
y <- [10,100,1000]
return (x * y)
以下是转化的并排比较:
do --
m -- m >> n
n --
do --
x <- m -- m >>= (\x ->
... -- ...)
理解表示法的最佳方法是首先理解>>=
和return
,因为正如我所说的那样,这就是编译器转换为符号的原因。
作为旁注,>>
与>>=
相同,它只是忽略了左参数的“结果”(尽管它保留了“上下文”或“结构”)。因此,>>
的所有定义必须等同于m >> n = m >>= (\_ -> n)
。
>>=
为了帮助驱动Monads通常不纯的点,让我们使用列表的Monad定义扩展第二个列表示例中的>>=
调用。定义是:
instance Monad [] where
return x = [x]
xs >>= f = concatMap f xs
我们可以将example2
转换为:
第0步(我们已有的)
example2 :: [Int]
example2 =
[1,2,3] >>=
(\x -> [10,100,1000] >>=
(\y -> return (x * y)))
第1步(转换第一个>>=
)
example2 =
concatMap
(\x -> [10,100,1000] >>=
(\y -> return (x * y)))
[1,2,3]
第2步
example2 =
concatMap
(\x -> concatMap
(\y -> return (x * y))
[10,100,1000])
[1,2,3]
第3步
example2 =
concatMap
(\x -> concatMap
(\y -> [x * y])
[10,100,1000])
[1,2,3]
所以,这里没有神奇的功能,只是正常的函数调用。
答案 1 :(得分:4)
您可以编写一个函数,其参数取决于另一个函数的评估:
-- Ads the first two elements of a list together
myFunc :: [Int] -> Int
myFunc xs = (head xs) + (head $ tail xs)
如果那就是你的意思。在这种情况下,您无法在不评估myFunc xs
,head xs
和head $ tail xs
的情况下获得(+)
的输出。这里有订单。但是,编译器可以选择执行head xs
和head $ tail xs
的顺序,因为它们不相互依赖,但它不能同时执行添加,而不会同时具有其他结果。它甚至可以选择并行评估它们,或者在不同的机器上评估它们。重点是纯函数,因为它们没有副作用,不必按给定的顺序进行评估,直到它们的结果相互依赖。
查看上述函数的另一种方法是图:
myFunc
|
(+)
/ \
/ \
head head
\ |
\ tail
\ /
xs
为了评估节点,必须首先评估其下的所有节点,但可以并行评估不同的分支。必须至少部分地评估第一个xs
,但之后可以并行评估两个分支。由于惰性评估存在一些细微差别,但这基本上是编译器构建评估树的方式。
如果您真的想在另一个之前强制执行一个函数调用,则可以使用seq
函数。它需要两个参数,强制第一个被评估,然后返回第二个参数,例如
myFunc2 :: [Int] -> Int
myFunc2 xs = hxs + (hxs `seq` (head $ tail xs))
where hxs = head xs
这将迫使head xs
在head $ tail xs
之前进行评估,但这更多地涉及严格性而非排序函数。
答案 2 :(得分:-1)
这是一个简单的方法:
case f x of
result1 -> case g y of
result2 -> ....
但是,除非g y
使用来自result1的内容以及来自result2
的后续计算,否则模式必须评估结果,否则无法保证{{1} }或f
实际上是被调用的,也不是以什么顺序调用的。
但是,你想要一种接一种调用一个函数的方法,这就是这种方式。