<NME> ioaction.py
<BEF> """Implementation of IOActions - "The Gods Must Be Crazy"

Many thanks to Chris Taylor and his excellent blog post "IO Is Pure",
http://chris-taylor.github.io/blog/2013/02/09/io-is-not-a-side-effect/

from .typing import Functor
from .typing import Monad
from .functor import Functor
from .monad import Monad


class IOAction(Monad, Applicative, Functor):

    def __init__(self, value=None):
        """A container for a value"""
    happen. Simply having an action lying around doesn't make anything
        super().__init__()
        self._get_value = lambda: value

    def bind(self, func) -> "IOAction":
        """IOAction a -> (a -> IOAction b) -> IOAction b"""

        return func(self._get_value())

    def apply(self, something) -> "IOAction":
        return something.fmap(self._get_value())

    def fmap(self, func) -> "IOAction":
        return IOAction(func(self._get_value()))

    def __call__(self, *args, **kwargs):
        """Run IO action. Nothing more to run."""

        return

    def __str__(self, m=0, n=0):
        a = self._get_value()

    def __or__(self, func):
        """Use | as operator for bind.
        return self.__str__()


class Put(IOAction):
    """A container holding a string to be printed to stdout, followed by
    another IOAction.
    """

    def __init__(self, text: str, action: IOAction):
        super().__init__()
        self.print_func = print
        self._get_value = lambda: (text, action)

    def bind(self, func) -> IOAction:
        """IOAction a -> (a -> IOAction b) -> IOAction b"""

        text, a = self._get_value()
        return Put(text, a.bind(func))

    def fmap(self, func) -> "IOAction":
        # Put s (fmap f io)
        text, action = self._get_value()
        return Put(text, action.fmap(func))
        return self.__str__()

        """Run IO action"""

        text, action = self._get_value()
        self.print_func("output: %s" % text)
        return action()

    def __str__(self, m=0, n=0):
    def map(self, func: Callable[[TSource], TResult]) -> "IO[TResult]":
        return Return(func(self._value))
        return '%sPut ("%s",\n%s\n%s)' % (ind(m), s, a, ind(m))


class Get(IOAction):
    """A container holding a function from string -> IOAction, which can be
    applied to whatever string is read from stdin.
    """

        return self._value

        self.input_func = input
        self._get_value = lambda: func

    def bind(self, func) -> IOAction:
        """IOAction a -> (a -> IOAction b) -> IOAction b"""

        g = self._get_value()
        return Get(lambda s: g(s).bind(func))

    def fmap(self, func) -> "IOAction":
        # Get (\s -> fmap f (g s))
        g = self._get_value()
        return Get(lambda s: g(s).fmap(func))

    def __call__(self, *args, **kwargs):
        """Run IO action"""

        func = self._get_value()
        action = func(self.input_func())
        return Put(text, io.bind(func))

    def map(self, func: Callable[[TSource], TResult]) -> "IO[TResult]":
        # Put s (fmap f io)
        assert self._value is not None
        a = (g(i)).__str__(m+2, n+1)
        return '%sGet (%s -> \n%s\n%s)' % (ind(m), i, a, ind(m))

def get():
    return Get(lambda s: IOAction(s))

def put(string):
    return Put(string, IOAction(()))

ind = lambda x: ' '*x

    def __call__(self, world: int = 0) -> TSource:
        return self.run(world)

    def __str__(self, m: int = 0, n: int = 0) -> str:
        g = self._fn
        i = "x%s" % n
        a = g(i).__str__(m + 1, n + 1)
        return "%sGet (%s => \n%s\n%s)" % (ind(m), i, a, ind(m))


class ReadFile(IO[str]):
    """A container holding a filename and a function from string -> IO[str],
    which can be applied to whatever string is read from the file.
    """

    def __init__(self, filename: str, func: Callable[[str], IO]) -> None:
        self.open_func = open
        self._value = filename, func

    def bind(self, func: Callable[[Any], IO]) -> IO:
        """IO a -> (a -> IO b) -> IO b"""

        filename, g = self._value
        return ReadFile(filename, lambda s: g(s).bind(func))

    def map(self, func: Callable[[Any], Any]) -> IO:
        # Get (\s -> fmap f (g s))
        filename, g = self._value
        return Get(lambda s: g(s).map(func))

    def run(self, world: int) -> str:
        """Run IO Action"""

        filename, func = self._value
        f = self.open_func(filename)
        action = func(f.read())
        return action(world=world + 1)

    def __call__(self, world: int = 0) -> str:
        return self.run(world)

    def __str__(self, m: int = 0, n: int = 0) -> str:
        filename, g = self._value
        i = "x%s" % n
        a = g(i).__str__(m + 2, n + 1)
        return '%sReadFile ("%s",%s => \n%s\n%s)' % (ind(m), filename, i, a, ind(m))


def get_line() -> IO[str]:
    return Get(Return)


def put_line(text: str) -> IO:
    return Put(text, Return(Unit))


def read_file(filename: str) -> IO:
    return ReadFile(filename, Return)


def pure_print(world: int, text: str) -> int:
    print(text)  # Impure. NOTE: If you see this line you need to wash your hands
    return world + 1


def pure_input(world: int) -> Tuple[int, str]:
    text = input()  # Impure. NOTE: If you see this line you need to wash your hands
    return (world + 1, text)


assert isinstance(IO, Functor)
assert isinstance(IO, Monad)

assert isinstance(Put, Functor)
assert isinstance(Put, Monad)

assert isinstance(Get, Functor)
assert isinstance(Get, Monad)

assert isinstance(ReadFile, Functor)
assert isinstance(ReadFile, Monad)

<MSG> Renames and fixes

<DFF> @@ -1,4 +1,4 @@
-"""Implementation of IOActions - "The Gods Must Be Crazy"
+"""Implementation of IO Actions - "The Gods Must Be Crazy"
 
 Many thanks to Chris Taylor and his excellent blog post "IO Is Pure",
 http://chris-taylor.github.io/blog/2013/02/09/io-is-not-a-side-effect/
@@ -8,8 +8,11 @@ from .applicative import Applicative
 from .functor import Functor
 from .monad import Monad
 
-
-class IOAction(Monad, Applicative, Functor):
+class IO(Monad, Applicative, Functor):
+    """IO Actions specify something that can be done. They are not active in
+    and of themselves. They need to be "run" to make something happen. Simply
+    having an action lying around doesn't make anything happen.
+    """
 
     def __init__(self, value=None):
         """A container for a value"""
@@ -17,21 +20,21 @@ class IOAction(Monad, Applicative, Functor):
         super().__init__()
         self._get_value = lambda: value
 
-    def bind(self, func) -> "IOAction":
-        """IOAction a -> (a -> IOAction b) -> IOAction b"""
+    def bind(self, func) -> "IO":
+        """IO a -> (a -> IO b) -> IO b"""
 
         return func(self._get_value())
 
-    def apply(self, something) -> "IOAction":
+    def apply(self, something) -> "IO":
         return something.fmap(self._get_value())
 
-    def fmap(self, func) -> "IOAction":
-        return IOAction(func(self._get_value()))
+    def fmap(self, func) -> "IO":
+        return IO(func(self._get_value()))
 
     def __call__(self, *args, **kwargs):
-        """Run IO action. Nothing more to run."""
+        """Nothing more to run."""
 
-        return
+        return IO(self._get_value())
 
     def __str__(self, m=0, n=0):
         a = self._get_value()
@@ -41,23 +44,23 @@ class IOAction(Monad, Applicative, Functor):
         return self.__str__()
 
 
-class Put(IOAction):
+class Put(IO):
     """A container holding a string to be printed to stdout, followed by
-    another IOAction.
+    another IO Action.
     """
 
-    def __init__(self, text: str, action: IOAction):
+    def __init__(self, text: str, action: IO):
         super().__init__()
         self.print_func = print
         self._get_value = lambda: (text, action)
 
-    def bind(self, func) -> IOAction:
-        """IOAction a -> (a -> IOAction b) -> IOAction b"""
+    def bind(self, func) -> IO:
+        """IO a -> (a -> IO b) -> IO b"""
 
         text, a = self._get_value()
         return Put(text, a.bind(func))
 
-    def fmap(self, func) -> "IOAction":
+    def fmap(self, func) -> "IO":
         # Put s (fmap f io)
         text, action = self._get_value()
         return Put(text, action.fmap(func))
@@ -66,7 +69,7 @@ class Put(IOAction):
         """Run IO action"""
 
         text, action = self._get_value()
-        self.print_func("output: %s" % text)
+        self.print_func("%s" % text)
         return action()
 
     def __str__(self, m=0, n=0):
@@ -75,8 +78,8 @@ class Put(IOAction):
         return '%sPut ("%s",\n%s\n%s)' % (ind(m), s, a, ind(m))
 
 
-class Get(IOAction):
-    """A container holding a function from string -> IOAction, which can be
+class Get(IO):
+    """A container holding a function from string -> IO, which can be
     applied to whatever string is read from stdin.
     """
 
@@ -85,19 +88,19 @@ class Get(IOAction):
         self.input_func = input
         self._get_value = lambda: func
 
-    def bind(self, func) -> IOAction:
-        """IOAction a -> (a -> IOAction b) -> IOAction b"""
+    def bind(self, func) -> IO:
+        """IO a -> (a -> IO b) -> IO b"""
 
         g = self._get_value()
         return Get(lambda s: g(s).bind(func))
 
-    def fmap(self, func) -> "IOAction":
+    def fmap(self, func) -> "IO":
         # Get (\s -> fmap f (g s))
         g = self._get_value()
         return Get(lambda s: g(s).fmap(func))
 
     def __call__(self, *args, **kwargs):
-        """Run IO action"""
+        """Run IO Action"""
 
         func = self._get_value()
         action = func(self.input_func())
@@ -109,10 +112,46 @@ class Get(IOAction):
         a = (g(i)).__str__(m+2, n+1)
         return '%sGet (%s -> \n%s\n%s)' % (ind(m), i, a, ind(m))
 
-def get():
-    return Get(lambda s: IOAction(s))
+class ReadFile(IO):
+    def __init__(self, filename, func):
+        super().__init__((filename, func))
+        self.open_func = open
+        self._get_value = lambda: (filename, func)
+
+    def bind(self, func) -> IO:
+        """IO a -> (a -> IO b) -> IO b"""
+
+        filename, g = self._get_value()
+        return ReadFile(filename, lambda s: g(s).bind(func))
+
+    def fmap(self, func) -> "IO":
+        # Get (\s -> fmap f (g s))
+        filename, g = self._get_value()
+        return Get(lambda s: g(s).fmap(func))
+
+    def __call__(self, *args, **kwargs):
+        """Run IO Action"""
+
+        filename, func = self._get_value()
+        f = self.open_func(filename)
+        action = func(f.read())
+        return action()
+
+    def __str__(self, m=0, n=0):
+        filename, g = self._get_value()
+        i = "$%s" % n
+        a = (g(i)).__str__(m+2, n+1)
+        return '%sReadFile ("%s",%s -> \n%s\n%s)' % (ind(m), filename, i, a, ind(m))
+
+
+def get_line() -> IO:
+    return Get(lambda s: IO(s))
+
+def put_line(string=None) -> IO:
+    return Put(string, IO(()))
 
-def put(string):
-    return Put(string, IOAction(()))
+def read_file(filename) -> IO:
+    return ReadFile(filename, lambda s: IO(s))
 
+# Utility for indentation
 ind = lambda x: ' '*x

Renames and fixes