How to view higher-order functions and IO-actions from a mathematical perspective?

后端 未结 3 1509
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-15 02:13

I am trying to understand functional programming from first principles, yet I am stuck on the interface between the pure functional world and the impure real world that has

3条回答
  •  北恋
    北恋 (楼主)
    2021-01-15 02:44

    IO is a data structure. E.g. here's a very simple model of IO:

    data IO a = Return a | GetLine (String -> IO a) | PutStr String (IO a)
    

    Real IO can be seen as being this but with more constructors (I prefer to think of all the IO "primitives" in base as such constructors). The main value of a Haskell program is just a value of this data structure. The runtime (which is "external" to Haskell) evaluates main to the first IO constructor, then "executes" it somehow, passes any values returned back as arguments to the contained function, and then executes the resulting IO action recursively, stopping at the Return (). That's it. IO doesn't have any strange interactions with functions, and it's not actually "impure", because nothing in Haskell is impure (unless it's unsafe). There is just an entity outside of your program that interprets it as something effectful.

    Thinking of functions as tables of inputs and outputs is perfectly fine. In mathematics, this is called the graph of the function, and e.g. in set theory it's often taken as the definition of a function in the first place. Functions that return IO actions fit just fine into this model. They just return values of the data structure IO; nothing strange about it. E.g. putStrLn might be defined as so (I don't think it actually is, but...):

    putStrLn s = PutStr (s ++ "\n") (Return ())
    

    and readLn could be

    -- this is actually read <$> getLine; real readLn throws exceptions instead of returning bottoms
    readLn = GetLine (\s -> Return (read s))
    

    both of which have perfectly sensible interpretations when thinking of functions as graphs.

    Your other question, about how to interpret higher-order functions, isn't going to get you very far. Functions are values, period. Modeling them as graphs is a good way to think about them, and in that case higher order functions look like graphs which contain graphs in their input or output columns. There's no "simplifying view" that turns a function taking a function or returning a function into a function that takes just values and returns just values. Such a process is not well-defined and is unnecessary.

    (Note: some people might try to tell you that IO can be seen as a function taking the "real world" as input and outputting a new version of the world. That's really not a good way to think about it, in part because it conflates evaluation and execution. It's a hack that makes implementing Haskell simpler, but it makes using and thinking about the language a bit of a mess. This data structure model is IMO easier to deal with.)

提交回复
热议问题