代码之家  ›  专栏  ›  技术社区  ›  WeNeedAnswers

Haskell真的是纯的吗(任何处理系统外输入和输出的语言)?

  •  32
  • WeNeedAnswers  · 技术社区  · 14 年前

    编辑:

    这不是火焰诱饵,因为有人在这篇文章中说,但一个真正的问题,我希望有人可以击落我说,证据,这是纯粹的。

    7 回复  |  直到 13 年前
        1
  •  78
  •   Sarfaraz Nawaz    7 年前

    采用以下迷你语言:

    data Action = Get (Char -> Action) | Put Char Action | End
    

    Get f 意思是:读一个字 c ,并执行操作 f c

    Put c a 意思:写字符 c ,并执行操作 a .

    下面是一个打印“xy”的程序,然后要求输入两个字母并按相反顺序打印:

    Put 'x' (Put 'y' (Get (\a -> Get (\b -> Put b (Put a End)))))
    

    conditionally p = Get (\a -> if a == 'Y' then p else End)
    

    这是我的类型 Action -> Action

    printString = foldr Put End
    

    String -> Action -它接受一个字符串并返回一个编写该字符串的程序,如

    Put 'h' (Put 'e' (Put 'l' (Put 'l' (Put 'o' End))))

    伊奥在哈斯克尔的作品相似。尽管 执行

    用像C这样的语言你可以写一个函数 void execute(Action a) main = a . 编译器创建了一个执行该操作的程序,但是您没有其他方法来执行该操作(除了肮脏的技巧)。

    Get Put

    添加结果值

    data IO a = Get (Char -> Action) | Put Char Action | End a
    

    上一个 Action IO () ,即始终返回“unit”的IO值,与“void”相当。

    这些是IO操作,可以以某种结果结束。这样的值:

    Get (\x -> if x == 'A' then Put 'B' (End 3) else End 4)
    

    有类型 IO Int 与C程序相对应:

    int f() {
      char x;
      scanf("%c", &x);
      if (x == 'A') {
        printf("B");
        return 3;
      } else return 4;
    }
    

    评估和执行是有区别的。您可以计算任何Haskell表达式,并得到一个值;例如,将2+2::Int求值为4::Int。只能执行类型为IO a的Haskell表达式。这可能有副作用;执行 Put 'a' (End 3) 把字母a放到屏幕上。如果计算IO值,如下所示:

    if 2+2 == 4 then Put 'A' (End 0) else Put 'B' (End 2)
    

    Put 'A' (End 0)
    

    副作用-你只做了评估,这是无害的。

    bool comp(char x) {
      char y;
      scanf("%c", &y);
      if (x > y) {       //Character comparison
        printf(">");
        return true;
      } else {
        printf("<");
        return false;
      }
    }
    

    变成IO值?

    固定一些字符,说“v”。现在 comp('v') 是一个IO操作,它将给定字符与“v”进行比较。同样地, comp('b') 是一个IO操作,它将给定字符与“b”进行比较。一般来说, comp 是一个接受字符并返回IO操作的函数。

    是布尔值。在C语言中,求值和执行是相同的(即它们的意思是相同的,或者 发生 同时)。不是在哈斯克尔。 变成一些IO动作 执行 给出一个布尔值(准确地说,它的计算结果是如上所述的代码块,只是用“b”代替了x。)

    comp :: Char -> IO Bool
    comp x = Get (\y -> if x > y then Put '>' (End True) else Put '<' (End False))
    

    comp 'b' 评估为 Get (\y -> if 'b' > y then Put '>' (End True) else Put '<' (End False)) .

    这在数学上也是有道理的。在C中, int f() 是一个函数。对于数学家来说,这是没有意义的-一个没有参数的函数?函数的重点是获取参数。函数 int f() 应等同于 int f

    头等舱

    这些IO值是一流的。就像你可以有一个整数元组列表一样 [[(0,2),(8,3)],[(2,8)]] 可以使用IO构建复杂的值。

     (Get (\x -> Put (toUpper x) (End 0)), Get (\x -> Put (toLower x) (End 0)))
       :: (IO Int, IO Int)
    

    IO操作的元组:第一个读取字符并将其打印为大写,第二个读取字符并返回小写。

     Get (\x -> End (Put x (End 0))) :: IO (IO Int)
    

    一个IO值,它读取一个字符 x 结束,返回一个IO值 到屏幕。

     sequence :: [IO a] -> IO [a]
    

    它获取一个IO操作列表,并返回一个按顺序执行它们的IO操作。

    单子

    单子是一些组合词(如 conditionally 上面),它允许您更结构化地编写程序。有一个函数

     IO a -> (a -> IO b) -> IO b
    

    a f() 第二个参数是 b g(a x) 它返回一个 g(f(x))

    注意,单子对纯度不是必需的-你可以像我上面所做的那样编写程序。

    纯度

    在哈斯克尔,如果你有 f x+f x 你可以用 2*f x f(x)+f(x) 一般来说不一样 2*f(x) f 可以在屏幕上打印或修改 .

    由于纯度,编译器有更多的自由度,可以更好地优化。它可以重新安排计算,而在C语言中,它必须考虑这是否会改变程序的含义。

        2
  •  9
  •   Community CDub    8 年前

    重要的是要明白,没有什么固有的特殊单子,所以他们肯定不代表阿吉特出狱卡在这方面。实现或使用monad不需要编译器(或其他)魔法,它们是在Haskell的纯函数环境中定义的。特别地, sdcvvc

        3
  •  6
  •   solidsnack    14 年前

    计算机系统“在黑板数学之外”的推理意味着什么?那是什么样的推理呢?航位推算?

    我们可以通过给它第二个论点,一个世界,并要求它在完成时传递给我们一个新的世界,使每一个影响函数的方面都变得纯粹。我不知道 C++ 除了说 read

    vector<char> read(filepath_t)
    

    在我们新的“纯风格”中,我们的处理方式如下:

    pair<vector<char>, world_t> read(world_t, filepath_t)
    

    事实上,每个Haskell IO操作都是这样工作的。

    • 盒子里有什么 world_t 结构?对每一粒沙子、每一片树叶的描述 草,破碎的心和金色的夕阳?

    • 我们有一个非正式的规则,我们只使用一次世界——在每次IO操作之后,我们 扔掉我们曾经的世界。尽管这些世界到处都是, 我们一定会把他们搞混的。

    第一个问题很简单。只要我们不允许观察这个世界,我们就不必费心在里面储存任何东西。我们只需要确保一个新的世界不等于任何一个以前的世界(以免编译器恶意优化一些产生世界的操作,就像在 C++ ). 有很多方法可以解决这个问题。

    至于世界被混为一谈,我们想把经过图书馆的世界藏起来,这样就没有办法得到世界,也就没有办法把它们混为一谈。原来,单子是在计算中隐藏“旁道”的好方法。输入IO单子。

    http://www.reddit.com/r/haskell/comments/8bhir/why_the_io_monad_isnt_a_dirty_hack/

        4
  •  4
  •   Edward    14 年前

    我对函数式编程非常陌生,但以下是我对它的理解:

    它们由main函数返回。

    简而言之,Haskell程序是一个函数,由其他函数组成,它返回一个命令式程序。Haskell程序本身是纯粹的。显然,这个命令式的程序本身是不可能的。现实世界的计算机从定义上讲是不纯的。

    这个问题还有很多,其中很多是语义问题(人类,而不是编程语言)。单子也比我在这里描述的要抽象一些。但我认为这是一个有用的方式来考虑它的一般。

        5
  •  4
  •   dino    14 年前

    我是这样想的:程序必须与外部世界做一些事情才能有用。当你(用任何语言)编写代码时,正在发生的(或应该发生的)是,你努力编写尽可能多的纯的、没有副作用的代码,并将IO限制在特定的地方。

    我们在Haskell中看到的是,你被更多地推向写作的这个方向,以严格控制效果。在核心和许多库中,也有大量的纯代码。哈斯克尔真的很关心这件事。哈斯克尔的单子在很多方面都很有用。它们的用途之一是围绕处理杂质的代码进行限制。

    如果我理解你说的是对的,我不认为这是假的,也不认为这只是在我们的头脑中,像“出狱免费卡”。这里的好处是非常真实的。

        6
  •  4
  •   sclv    14 年前

    对于sdcwc构建IO的扩展版本,可以查看Hackage上的IOSpec包: http://hackage.haskell.org/package/IOSpec

        7
  •  1
  •   atravers    4 年前

    哈斯克尔真的纯洁吗?[…]

    Haskell 1.2 . 那时候 main 是:

    main :: [Response] -> [Request]
    

    main :: Dialogue
    

    哪里:

    type Dialogue = [Response] -> [Request]
    

    Response 随着 Request 尽管数据类型很大,但它们并不重要:

    pared-down definitions of request and response datatypes for dialogue-based I/O

    Haskell中使用一元接口的I/O的出现改变了这一切——不再是可见的数据类型,只是一个抽象的规范。结果呢,怎么办 IO , return , (>>=) etc的定义现在特定于Haskell的每个实现。

    • I/O在吗 哈斯克尔纯?

    正如欧文·斯蒂芬斯在书中所说 Approaches to Functional I/O

    I/O不是一个特别活跃的研究领域,但新的方法仍在被发现[…]

    招来这么多恶名。。。

        8
  •  -2
  •   dev1223    10 年前

    不,不是。IO monad是不纯的,因为它有副作用和可变状态(Haskell程序中的竞争条件是可能的,所以?呃。。。纯FP语言不知道“种族条件”之类的东西。真正纯粹的FP是干净的唯一性类型,或者Elm是FRP(函数反应式编程)而不是Haskell。哈斯克尔是个大谎言。

    证明:

    import Control.Concurrent 
    import System.IO as IO
    import Data.IORef as IOR
    
    import Control.Monad.STM
    import Control.Concurrent.STM.TVar
    
    limit = 150000
    threadsCount = 50
    
    -- Don't talk about purity in Haskell when we have race conditions 
    -- in unlocked memory ... PURE language don't need LOCKING because
    -- there isn't any mutable state or another side effects !!
    
    main = do
        hSetBuffering stdout NoBuffering
        putStr "Lock counter? : "
        a <- getLine
        if a == "y" || a == "yes" || a == "Yes" || a == "Y"
        then withLocking
        else noLocking
    
    noLocking = do
        counter <- newIORef 0
        let doWork = 
            mapM_ (\_ -> IOR.modifyIORef counter (\x -> x + 1)) [1..limit]
        threads <- mapM (\_ -> forkIO doWork) [1..threadsCount]
        -- Sorry, it's dirty but time is expensive ...
        threadDelay (15 * 1000 * 1000)
        val <- IOR.readIORef counter
        IO.putStrLn ("It may be " ++ show (threadsCount * limit) ++ 
            " but it is " ++ show val) 
    
    withLocking = do
        counter <- atomically (newTVar 0)
        let doWork = 
            mapM_ (\_ -> atomically $ modifyTVar counter (\x -> 
                x + 1)) [1..limit]
        threads <- mapM (\_ -> forkIO doWork) [1..threadsCount]
        threadDelay (15 * 1000 * 1000)
        val <- atomically $ readTVar counter
        IO.putStrLn ("It may be " ++ show (threadsCount * limit) ++ 
            " but it is " ++ show val)