Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ianj-als/pypeline.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Johnson <ian.johnson@appliedlanguage.com>2013-08-20 13:58:10 +0400
committerIan Johnson <ian.johnson@appliedlanguage.com>2013-08-20 13:58:10 +0400
commit8e366c3579c6e8a1d749b5a6dd7fa5afd4bb452e (patch)
tree7e8a1aeb500546e8662020f2a9247d8263f7b36c
parentf4be4abe3c8e1965f5f68f291c9caabfdccd4d0b (diff)
Started messing with the continuation monad.
-rw-r--r--setup.py2
-rw-r--r--src/pypeline/__init__.py5
-rw-r--r--src/pypeline/core/arrows/function_arrow_choice.py2
-rw-r--r--src/pypeline/core/types/cont.py67
-rw-r--r--src/pypeline/core/types/just.py8
-rw-r--r--src/pypeline/core/types/monad.py3
-rw-r--r--src/pypeline/core/types/nothing.py12
-rw-r--r--src/pypeline/core/types/state.py18
-rw-r--r--src/pypeline/core/types/tests/cont_tests.py66
9 files changed, 165 insertions, 18 deletions
diff --git a/setup.py b/setup.py
index 641ce72..0b32104 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ from setuptools import setup, find_packages
setup(
name = "pypeline",
- version = "0.2.7",
+ version = "0.3.0",
packages = find_packages("src", exclude = ["*tests"]),
package_dir = {'': 'src'},
diff --git a/src/pypeline/__init__.py b/src/pypeline/__init__.py
index 761ed8b..09c66e9 100644
--- a/src/pypeline/__init__.py
+++ b/src/pypeline/__init__.py
@@ -16,3 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pypeline. If not, see <http://www.gnu.org/licenses/>.
#
+import sys
+
+if sys.version_info < (2, 7):
+ raise Exception("Python version too old, please use 2.7 or newer.")
+
diff --git a/src/pypeline/core/arrows/function_arrow_choice.py b/src/pypeline/core/arrows/function_arrow_choice.py
index a90326b..7334f4b 100644
--- a/src/pypeline/core/arrows/function_arrow_choice.py
+++ b/src/pypeline/core/arrows/function_arrow_choice.py
@@ -114,7 +114,7 @@ def test(arrow):
#
# This maker returns an arrow that implements if
#
-# if_maker :: (b -> c) -> (b -> d) -> (b -> d) -> a b (Either d d)
+# if_maker :: (b -> c) -> (b -> d) -> (b -> d) -> a b d
#
def if_maker(predicate_func, then_func, else_func):
return test(FunctionArrow(predicate_func)) >> (FunctionArrowChoice(then_func) | FunctionArrowChoice(else_func))
diff --git a/src/pypeline/core/types/cont.py b/src/pypeline/core/types/cont.py
new file mode 100644
index 0000000..056414d
--- /dev/null
+++ b/src/pypeline/core/types/cont.py
@@ -0,0 +1,67 @@
+#
+# Copyright Applied Language Solutions 2012
+#
+# This file is part of Pypeline.
+#
+# Pypeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pypeline is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Pypeline. If not, see <http://www.gnu.org/licenses/>.
+#
+import types
+
+from pypeline.core.types.monad import Monad
+
+
+# Continuation monad
+class Cont(Monad):
+ def __init__(self, a):
+ super(Monad, self).__init__()
+ if type(a) is not types.FunctionType and \
+ type(a) is not types.MethodType:
+ raise ValueError("Must be a function or method")
+
+ self._cont = a
+
+ @staticmethod
+ def return_(a):
+ return Cont(a)
+
+ # Bind operator
+ # (>>=) :: m a -> (a -> m b) -> m b
+ def __ge__(self, f):
+ if type(f) is not types.FunctionType and \
+ type(f) is not types.MethodType:
+ raise ValueError("Must be a function or method")
+
+ return Cont(lambda k: Cont.runCont(self, lambda a: Cont.runCont(f(a), k)))
+
+ # Run a continuation
+ # runCont :: Cont r a -> (a -> r) -> r
+ @staticmethod
+ def runCont(c, f):
+ return c._cont(f)
+
+
+# return_ :: a -> Cont r a
+def return_(a):
+ return Cont.return_(lambda k: k(a))
+
+
+# Call current continuation
+# callCC :: ((a -> Cont r b) -> Cont r a) -> Cont r a
+def callCC(f):
+ if type(f) is not types.FunctionType and \
+ type(f) is not types.MethodType:
+ raise ValueError("Must be a function or method")
+
+ cont = lambda k: Cont.runCont(f(lambda a: Cont(lambda: k(a)), k))
+ return Cont(cont)
diff --git a/src/pypeline/core/types/just.py b/src/pypeline/core/types/just.py
index 07d3c23..be84da4 100644
--- a/src/pypeline/core/types/just.py
+++ b/src/pypeline/core/types/just.py
@@ -26,14 +26,16 @@ from pypeline.core.types.monad import Maybe
#
class Just(Maybe):
def __init__(self, a):
+ super(Maybe, self).__init__()
if a is None:
raise ValueError("Value cannot be None")
self._a = a
# return
# return :: a -> m a
- def return_(self, a):
- return return_(a)
+ @staticmethod
+ def return_(a):
+ return Just(a)
def __ge__(self, function):
if type(function) is not types.FunctionType and \
@@ -65,4 +67,4 @@ class Just(Maybe):
def return_(a):
- return Just(a)
+ return Just.return_(a)
diff --git a/src/pypeline/core/types/monad.py b/src/pypeline/core/types/monad.py
index a6ff269..62c9781 100644
--- a/src/pypeline/core/types/monad.py
+++ b/src/pypeline/core/types/monad.py
@@ -44,5 +44,6 @@ class Monad(object):
# Erm, maybe...
#
class Maybe(Monad):
- pass
+ def __init__(self):
+ super(Monad, self).__init__()
diff --git a/src/pypeline/core/types/nothing.py b/src/pypeline/core/types/nothing.py
index 9d6e7e2..a31c8b7 100644
--- a/src/pypeline/core/types/nothing.py
+++ b/src/pypeline/core/types/nothing.py
@@ -30,8 +30,12 @@ class Nothing(Maybe):
cls._instance = super(Nothing, cls).__new__(cls, *args, **kwargs)
return cls._instance
- def return_(self, a):
- return return_(a)
+ def __init__(self, *a):
+ pass
+
+ @staticmethod
+ def return_(a):
+ return Nothing()
def __eq__(self, other):
return Nothing._instance is other
@@ -49,5 +53,5 @@ class Nothing(Maybe):
return False
-def return_(a):
- return Nothing()
+def return_(*a):
+ return Nothing.return_(a)
diff --git a/src/pypeline/core/types/state.py b/src/pypeline/core/types/state.py
index 2c47e15..1a2c390 100644
--- a/src/pypeline/core/types/state.py
+++ b/src/pypeline/core/types/state.py
@@ -35,16 +35,18 @@ from pypeline.core.types.monad import Monad
#
class State(Monad):
def __init__(self, a):
- if type(a) is types.FunctionType or \
- type(a) is types.MethodType:
- self._func = a
- else:
- self._func = lambda s: (a, s)
+ super(Monad, self).__init__()
+ if type(a) is not types.FunctionType and \
+ type(a) is not types.MethodType:
+ raise ValueError("Must be a function or method")
+
+ self._func = a
# return
# return :: a -> m a
- def return_(self, a):
- return return_(a)
+ @staticmethod
+ def return_(a):
+ return State(lambda s: (a, s))
# (>>=) :: State s a -> (a -> State s b) -> State s b
# (State h) >>= f = State $ \s -> let (a, newState) = h s
@@ -83,4 +85,4 @@ class State(Monad):
def return_(a):
- return State(a)
+ return State.return_(a)
diff --git a/src/pypeline/core/types/tests/cont_tests.py b/src/pypeline/core/types/tests/cont_tests.py
new file mode 100644
index 0000000..48edfe6
--- /dev/null
+++ b/src/pypeline/core/types/tests/cont_tests.py
@@ -0,0 +1,66 @@
+#
+# Copyright Applied Language Solutions 2012
+#
+# This file is part of Pypeline.
+#
+# Pypeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pypeline is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Pypeline. If not, see <http://www.gnu.org/licenses/>.
+#
+import unittest
+import os
+import sys
+
+from pypeline.core.types.cont import Cont, return_, callCC
+
+
+class ContMonadUnitTest(unittest.TestCase):
+ def test_single_value(self):
+ value = 7
+ cont = return_(value)
+ result = Cont.runCont(cont, lambda x: x)
+ self.assertEquals(value, result)
+
+
+ def test_bind(self):
+ square_cps = lambda x: return_(x ** 2)
+ add_three_cps = lambda x: return_(x + 3)
+ result = Cont.runCont(square_cps(4) >= add_three_cps, lambda x: x)
+ self.assertEquals(19, result)
+
+
+ def test_desugared_do_notation(self):
+ # do x_squared <- square_cont x
+ # y_squared <- square_cont y
+ # sum_of_squares <- add_cont x_squared y_squared
+ # return sum_of_squares
+ add_cps = lambda x, y: return_(x + y)
+ square_cps = lambda x: return_(x ** 2)
+ pythagoras_cps = lambda x, y: (square_cps(x) >=
+ ((lambda x_squared: square_cps(y) >=
+ ((lambda y_squared: add_cps(x_squared, y_squared) >=
+ (lambda sum_of_squares: return_(sum_of_squares)))))))
+ result = Cont.runCont(pythagoras_cps(3, 4), lambda x: x)
+ self.assertEquals(25, result)
+
+
+ def test_call_current_continuation(self):
+ # divide_cps :: Int -> Int -> (String -> Cont r Int) -> Cont r Int
+ def divide_cps(x, y, k):
+ return callCC(lambda ok: callCC(lambda not_ok: not_ok("Divide by zero error") if y is 0 else ok(x / y)) >=
+ (lambda err: k(err)))
+
+ error = lambda err: sys.stderr.write(err + os.linesep)
+
+ print Cont.runCont(divide_cps(10, 2, error), lambda x: x)
+ print Cont.runCont(divide_cps(10, 0, error), lambda x: x)
+ self.assertTrue(False)