diff options
author | Ian Johnson <ian.johnson@appliedlanguage.com> | 2013-08-20 13:58:10 +0400 |
---|---|---|
committer | Ian Johnson <ian.johnson@appliedlanguage.com> | 2013-08-20 13:58:10 +0400 |
commit | 8e366c3579c6e8a1d749b5a6dd7fa5afd4bb452e (patch) | |
tree | 7e8a1aeb500546e8662020f2a9247d8263f7b36c | |
parent | f4be4abe3c8e1965f5f68f291c9caabfdccd4d0b (diff) |
Started messing with the continuation monad.
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | src/pypeline/__init__.py | 5 | ||||
-rw-r--r-- | src/pypeline/core/arrows/function_arrow_choice.py | 2 | ||||
-rw-r--r-- | src/pypeline/core/types/cont.py | 67 | ||||
-rw-r--r-- | src/pypeline/core/types/just.py | 8 | ||||
-rw-r--r-- | src/pypeline/core/types/monad.py | 3 | ||||
-rw-r--r-- | src/pypeline/core/types/nothing.py | 12 | ||||
-rw-r--r-- | src/pypeline/core/types/state.py | 18 | ||||
-rw-r--r-- | src/pypeline/core/types/tests/cont_tests.py | 66 |
9 files changed, 165 insertions, 18 deletions
@@ -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) |