除Monad之外,还有哪些其他方式可以用纯粹的功能语言来处理? [英] What other ways can state be handled in a pure functional language besides with Monads?

查看:79
本文介绍了除Monad之外,还有哪些其他方式可以用纯粹的功能语言来处理?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我开始把头围绕Monads(在Haskell中使用)。我很好奇IO或状态可以用纯粹的功能语言(理论上还是现实上)处理的其他方式。例如,有一种称为汞的逻辑语言,它使用效果类型。在诸如haskell的程序中,效果类型工作将如何?其他系统如何工作?

解决方案

这里涉及到几个不同的问题。 IO State 是完全不同的东西。 State 很容易做到
:只需向每个函数传递一个额外的参数,并返回一个额外的
结果,并且您有一个有状态函数;例如,转到 a - > b 转换为
a - > s - > (b,s)



这里没有任何魔法: Control.Monad.State 提供了一个封装器,
使得 s - >形式的状态动作 (a,s)方便,以及
作为一堆辅助函数,但就是这样。



I / O,就其性质而言,其实施必须具有一定的魔力。但是有很多表达I / O的方法在Haskell中并不涉及单词monad。
如果我们现在有一个没有IO的Haskell子集,并且我们想从
scratch开发IO,但不知道monad的任何内容,我们可能会做很多事情来做
。 / p>

例如,如果我们想要做的只是打印到stdout,我们可能会说:

 键入PrintOnlyIO =字符串

main :: PrintOnlyIO
main =Hello world!

然后有一个RTS(运行时系统)来评估字符串并打印它。
这让我们编写任何Haskell程序,其I / O完全由打印
到标准输出。



然而,这并不是非常有用,因为我们想要交互性!所以让我们来创造一个新的IO类型
,它允许它。想到最简单的事情是:

  type InteractIO = String  - >字符串

main :: InteractIO
main =映射toUpper

这种IO方法让我们可以编写任何从stdin读取并写入
stdout的代码(Prelude带有一个函数 interact :: InteractIO - > IO()
,顺便说一句)。



这样好多了,因为它可以让我们编写交互式程序。但是,与所有我们想要做的IO相比,它仍然非常有限,并且还有相当多的
容易出错(如果我们不小心尝试读入stdin太远,程序
将会阻止直到用户输入更多)。



我们希望能够做的不仅仅是读取标准输入和写入标准输出。以下是
早期版本的Haskell做了I / O的情况:大约: GetLine |退出| ...
data Response =成功| Str String | ...
type DialogueIO = [Response] - > [请求]

main :: DialogueIO
main resps1 =
PutStrLn你叫什么名字?
:GetLine
:$ b $的案例resps1成功:str名称:resps2 - >
PutStrLn(hi++ name ++!)
:退出

当我们编写 main 时,我们得到一个懒惰的列表参数,并返回一个懒惰列表作为
结果。我们返回的惰性列表的值为 PutStrLn s GetLine ;
之后,我们可以检查
(response)列表的下一个元素,RTS将安排它作为对
请求的响应。 / p>

有很多方法可以使这个机制更好地工作,但正如您可以想象的那样,这种方法很快就会变得非常尴尬。此外,它的错误倾向与前一种方法相同。



下面是另一种方法,它的错误率很低,概念上非常低
接近Haskell IO的实际行为:

  data ContIO =退出| PutStrLn字符串Cont | | GetLine(字符串 - > ContIO)| ... 

main :: ContIO
main =
PutStrLn你叫什么名字? $
GetLine $ \\\
ame - >
PutStrLn(hi++ name ++!)$
退出

关键在于,我们并不是在主开始时将一个懒惰列表作为一个大的
参数,而是一次接受一个
参数的单个请求。



我们的程序现在只是一个常规的数据类型 - 很像链接列表,除了
,您不能只是正常地遍历它:当RTS解释 main ,有时是
,它遇到一个像 GetLine 这样的值,它包含一个函数;那么它必须使用RTS魔术从标准输入中获得
字符串,并在该字符串可以继续之前将该字符串传递给函数
。练习:编写 interpret :: ContIO - > IO()



请注意,这些实现都不涉及世界传递。
世界传递并不是真正的I / O如何在Haskell中工作。 GHC中 IO 类型的实际
实现涉及一个名为
RealWorld 的内部类型,但这只是一个实现细节。

实际Haskell IO 添加了一个类型参数,因此我们可以编写这样的动作:
产生任意值 - 因此它看起来更像是数据IO a =完成a |
PutStr字符串(IO a)| GetLine(字符串 - > IO a)| ......
。这给了我们更多的
灵活性,因为我们可以创建产生任意
值的 IO 行动。



(正如Russell O'Connor 指出的那样,这种类型的
是只是一个免费的monad,我们可以很容易地为它写一个 Monad 实例。)






monads在哪里进入呢?事实证明,对于
I / O,我们不需要 Monad ,并且我们不需要 Monad 的答案是我们没有。对于类型 Monad



没有什么不可思议的。然而,当我们使用 IO State (以及列表和函数和
也许以及解析器和延续 - 持续的风格和......)足够长的时间,我们
最终发现他们在某些方面表现非常相似。我们可能
编写一个函数打印列表中的每一个字符串,并且一个函数在列表中运行
每个有状态计算并且通过状态线程,并且
看起来非常类似彼此。

因为我们不喜欢写很多类似外观的代码,所以我们想要一种方法来
抽象它; Monad 事实证明是一个很好的抽象,因为它让我们
抽象出很多类似的东西,但它们仍然提供了很多有用的
功能(包括 Control.Monad 中的所有内容)。



给定 bindIO :: IO a - > (a→IO b)→> IO b returnIO :: a - > IO a ,我们
可以在Haskell中编写任何 IO 程序,而不必考虑monad。但是
可能最终会复制 Control.Monad
中的很多函数,如 mapM (> =>)时,c $ c>和 forever code $。

通过实现常见的 Monad API,我们可以使用完全相同的代码
与IO操作一起使用,就像我们对解析器和列表所做的一样。这是我们获得 Monad 类的唯一
原因,以捕获
不同类型之间的相似性。


So I started to wrap my head around Monads (used in Haskell). I'm curious what other ways IO or state can be handled in a pure functional language (both in theory or reality). For example, there is a logical language called "mercury" that uses "effect-typing". In a program such as haskell, how would effect-typing work? How does other systems work?

解决方案

There are several different questions involved here.

First, IO and State are very different things. State is easy to do yourself: Just pass an extra argument to every function, and return an extra result, and you have a "stateful function"; for example, turn a -> b into a -> s -> (b,s).

There's no magic involved here: Control.Monad.State provides a wrapper that makes working with "state actions" of the form s -> (a,s) convenient, as well as a bunch of helper functions, but that's it.

I/O, by its nature, has to have some magic in its implementation. But there are a lot of ways of expressing I/O in Haskell that don't involve the word "monad". If we had an IO-free subset of Haskell as-is, and we wanted to invent IO from scratch, without knowing anything about monads, there are many things we might do.

For example, if all we want to do is print to stdout, we might say:

type PrintOnlyIO = String

main :: PrintOnlyIO
main = "Hello world!"

And then have an RTS (runtime system) which evaluates the string and prints it. This lets us write any Haskell program whose I/O consists entirely of printing to stdout.

This isn't very useful, however, because we want interactivity! So let's invent a new type of IO which allows for it. The simplest thing that comes to mind is

type InteractIO = String -> String

main :: InteractIO
main = map toUpper

This approach to IO lets us write any code which reads from stdin and writes to stdout (the Prelude comes with a function interact :: InteractIO -> IO () which does this, by the way).

This is much better, since it lets us write interactive programs. But it's still very limited compared to all the IO we want to do, and also quite error-prone (if we accidentally try to read too far into stdin, the program will just block until the user types more in).

We want to be able to do more than read stdin and write stdout. Here's how early versions of Haskell did I/O, approximately:

data Request = PutStrLn String | GetLine | Exit | ...
data Response = Success | Str String | ...
type DialogueIO = [Response] -> [Request]

main :: DialogueIO
main resps1 =
    PutStrLn "what's your name?"
  : GetLine
  : case resps1 of
        Success : Str name : resps2 ->
            PutStrLn ("hi " ++ name ++ "!")
          : Exit

When we write main, we get a lazy list argument and return a lazy list as a result. The lazy list we return has values like PutStrLn s and GetLine; after we yield a (request) value, we can examine the next element of the (response) list, and the RTS will arrange for it to be the response to our request.

There are ways to make working with this mechanism nicer, but as you can imagine, the approach gets pretty awkward pretty quickly. Also, it's error-prone in the same way as the previous one.

Here's another approach which is much less error-prone, and conceptually very close to how Haskell IO actually behaves:

data ContIO = Exit | PutStrLn String ContIO | GetLine (String -> ContIO) | ...

main :: ContIO
main =
    PutStrLn "what's your name?" $
    GetLine $ \name ->
    PutStrLn ("hi " ++ name ++ "!") $
    Exit

The key is that instead of taking a "lazy list" of responses as one big argument at he beginning of main, we make individual requests that accept one argument at a time.

Our program is now just a regular data type -- a lot like a linked list, except you can't just traverse it normally: When the RTS interprets main, sometimes it encounters a value like GetLine which holds a function; then it has to get a string from stdin using RTS magic, and pass that string to the function, before it can continue. Exercise: Write interpret :: ContIO -> IO ().

Note that none of these implementations involve "world-passing". "world-passing" isn't really how I/O works in Haskell. The actual implementation of the IO type in GHC involves an internal type called RealWorld, but that's only an implementation detail.

Actual Haskell IO adds a type parameter so we can write actions that "produce" arbitrary values -- so it looks more like data IO a = Done a | PutStr String (IO a) | GetLine (String -> IO a) | .... That gives us more flexibility, because we can create "IO actions" that produce arbitrary values.

(As Russell O'Connor points out, this type is just a free monad. We can write a Monad instance for it easily.)


Where do monads come into it, then? It turns out that we don't need Monad for I/O, and we don't need Monad for state, so why do we need it at all? The answer is that we don't. There's nothing magical about the type class Monad.

However, when we work with IO and State (and lists and functions and Maybe and parsers and continuation-passing style and ...) for long enough, we eventually figure out that they behave pretty similarly in some ways. We might write a function that prints every string in a list, and a function that runs every stateful computation in a list and threads the state through, and they'll look very similar to each other.

Since we don't like writing a lot of similar-looking code, we want a way to abstract it; Monad turns out to be a great abstraction, because it lets us abstract many types that seem very different, but still provide a lot of useful functionality (including everything in Control.Monad).

Given bindIO :: IO a -> (a -> IO b) -> IO b and returnIO :: a -> IO a, we could write any IO program in Haskell without ever thinking about monads. But we'd probably end up replicating a lot of the functions in Control.Monad, like mapM and forever and when and (>=>).

By implementing the common Monad API, we get to use the exact same code for working with IO actions as we do with parsers and lists. That's really the only reason we have the Monad class -- to capture the similarities between different types.

这篇关于除Monad之外,还有哪些其他方式可以用纯粹的功能语言来处理?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆