diff options
-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/arrows/tests/function_arrow_tests.py | 95 | ||||
-rw-r--r-- | src/pypeline/core/types/cont.py | 71 | ||||
-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 | 65 |
10 files changed, 232 insertions, 49 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/arrows/tests/function_arrow_tests.py b/src/pypeline/core/arrows/tests/function_arrow_tests.py index c5184ba..b38e33c 100644 --- a/src/pypeline/core/arrows/tests/function_arrow_tests.py +++ b/src/pypeline/core/arrows/tests/function_arrow_tests.py @@ -26,7 +26,7 @@ class ArrowUnitTests(unittest.TestCase): # Arrow law tests # - # Identity + # arr id = id def test_identity(self): iden = lambda x: x arrow = FunctionArrow(iden) @@ -48,6 +48,69 @@ class ArrowUnitTests(unittest.TestCase): self.assertEquals(arrow_func_comp(value), arrow_comp(value)) + # first (arr f) = arr (first f) + def first_arr_f_is_arr_first_f(self): + f = lambda x: x + 1 + fst = lambda x: x[0] + 1 + + arrow_one = FunctionArrow(f).first() + arrow_two = FunctionArrow(fst) + + value = (8, -10) + self.assertEquals(arrow_one(value), arrow_two(value)) + + + # first (a >>> b) = first a >>> first b + def test_first_a_b_is_first_a_first_b(self): + a = lambda x: x * 9 + b = lambda x: x - 7 + c = lambda x: b(a(x)) + + arrow_one = FunctionArrow(c).first() + arrow_two = FunctionArrow(a).first() >> FunctionArrow(b).first() + + value = (7, 21) + self.assertEquals(arrow_one(value), arrow_two(value)) + + + # first f >>> arr fst = arr fst >>> f + def test_first_arrow_fst_is_arrow_fst_func(self): + fst = lambda x: x[0] + f = lambda x: x * -9 + + arrow_one = FunctionArrow(f).first() >> FunctionArrow(fst) + arrow_two = FunctionArrow(fst) >> FunctionArrow(f) + + value = (-3, 19) + self.assertEquals(arrow_one(value), arrow_two(value)) + + + # first f >>> arr (id *** g) = arr (id *** g) >>> first f + def first_f_arr_id_g_is_arr_id_g_first_f(self): + id = lambda x: x + f = lambda x: x * -3 + g = lambda x: x - 9 + + arrow_one = FunctionArrow(f).first() >> (FunctionArrow(id) * FunctionArrow(g)) + arrow_two = (FunctionArrow(id) * FunctionArrow(g)) >> FunctionArrow(f).first() + + value = (-9, 8) + self.assertEquals(arrow_one(value), arrow_two(value)) + + + # first (first f) >>> arr assoc = arr assoc >>> first f + # where assoc((a, b), c) = (a, (b, c)) + def first_first_f_arr_assoc_is_arr_assoc_first_f(self): + assoc = lambda ab, c: (ab[0], (ab[1], c)) + f = lambda x: x + 3 + + arrow_one = FunctionArrow(f).first().first() >> FunctionArrow(assoc) + arrow_two = FunctionArrow(assoc) >> FunctionArrow(f).first() + + value = ((1, 2), 3) + self.assertEquals(arrow_one(value), arrow_two(value)) + + # arr id >>> a = a = a >>> arr id def test_arrow_id_func_is_func_is_func_comp_arrow(self): a = lambda x: x + 1 @@ -61,12 +124,6 @@ class ArrowUnitTests(unittest.TestCase): self.assertEquals(arrow_two(value), a(value)) - # first a >>> arr pi_1 = arr pi_1 >>> a - def test_first_arrow_pi_is_arrow_pi_func(self): - # Not sure what pi_1 is in this law - pass - - # first a >>> arr (id x f) = arr (id x f) >>> first a # first a >>> second f = second f >>> first a def test_first_arr_id_f_is_arr_id_f_first(self): @@ -80,30 +137,6 @@ class ArrowUnitTests(unittest.TestCase): self.assertEquals(arrow_one(value), arrow_two(value)) - # first a >>> arr alpha = arr alpha >>> first (first a) - def test_first_a_comp_arr_alpha_is_arr_alpha_first_first_a(self): - # not sure what alpha is in this law - pass - - - # first (arr f) = arr (f x id) - # first f = arr (first f) - # first f = first f - - - # first (a >>> b) = first a >>> first b - def test_first_a_b_is_first_a_first_b(self): - a = lambda x: x * 9 - b = lambda x: x - 7 - c = lambda x: b(a(x)) - - arrow_one = FunctionArrow(c).first() - arrow_two = FunctionArrow(a).first() >> FunctionArrow(b).first() - - value = (7, 21) - self.assertEquals(arrow_one(value), arrow_two(value)) - - # # Ad hoc tests # diff --git a/src/pypeline/core/types/cont.py b/src/pypeline/core/types/cont.py new file mode 100644 index 0000000..91614f5 --- /dev/null +++ b/src/pypeline/core/types/cont.py @@ -0,0 +1,71 @@ +# +# 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") + + def function(k): + def function_arg(a): + return Cont(lambda x: k(a)) + return Cont.runCont(f(function_arg), k) + + return Cont(function) 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..13ad05f --- /dev/null +++ b/src/pypeline/core/types/tests/cont_tests.py @@ -0,0 +1,65 @@ +# +# 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: Cont(lambda _: sys.stderr.write(err + os.linesep)) + + self.assertEquals(3, Cont.runCont(divide_cps(10, 3, error), lambda x: x)) + self.assertEquals(None, Cont.runCont(divide_cps(10, 0, error), lambda x: x)) |