Source code for functle

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import annotations

import contextlib
import functools
import turtle
import enum
import typing

import pint

ureg = pint.UnitRegistry()


# Side Note: Yes, Python has enums !
[docs]class PenState(enum.Enum): UP = -1 DOWN = 1
# Ref : https://vimeo.com/162054542
[docs]def schoenfinkel(fun): """ Curry the function : changes some parameters (the ones not passed at first) into a later function call. Effectively splits one call into two. >>> def add(a,b): ... return a + b >>> addfirst = curry(add) >>> andthen = addfirst(2) >>> addthen(3) 5 """ def curried(*args, **kwargs): # TODO : check type signature vs traditional function (haskell and others) # # TODO TODO TODO : not partial when all parameters have been passed, just apply ! p = functools.partial(fun, *args, **kwargs) return p return curried
# TODO : there is probably a more straightforward way fo defining curry from a functools decorator function curry = schoenfinkel @schoenfinkel def compose(*args): """A very simple composer, or "application accumulator". Will work only with curried functions, and is itself curried, to be able to use it freely. Note : This is just a Monoid, build on identity and the function application. >>> add2 = lambda x: (2 + x) >>> compose(add2) (compose(add2, add2)) (3) 9 """ acc = args[-1] if args else lambda x: x # monoid unit : the identity function for a in args[:-1][::-1]: # taking args list, except last element, in inverse order. # if one of the function we compose is a constant, we treat it as the constant function # and compose with it by erasing the accumulated result. if not callable(a): acc = a else: if callable(acc): acc = functools.partial(a, acc) # partial call because acc is not fully applied just yet else: acc = a(acc) return acc
[docs]@contextlib.contextmanager def functle(): """ A context in which a turtle is available, along with its state, and a functional application accumulator""" ft = turtle.Turtle() yield ft, TurtleState(position=ft.position(), angle=ft.heading(), pen=ft.pen().get('pendown'))
# TODO : exit cleanly # nametuple because it is portable, well known, and simple to use. However see http://www.attrs.org/en/stable/why.html#namedtuples # LATER : dataclasses, or frozen attrs.
[docs]class TurtleState(typing.NamedTuple): """ Immutable public State. """ position: turtle.Vec2D = turtle.Vec2D(0, 0) angle: int = 1 * ureg.degrees # pint and types ??? pen: PenState = PenState.DOWN
def move(distance: int, impl: turtle.Turtle, state: TurtleState): # TMP HACK distance = int(distance) impl.forward(distance=distance) # return new state (it is immutable) return impl, TurtleState(position=impl.position(), angle=impl.heading(), pen=impl.pen().get('pendown')) def right(angle: int, impl: turtle.Turtle, state: TurtleState): # TMP HACK angle = int(angle * ureg.degrees) impl.right(angle) # return new state (it is immutable) return impl, TurtleState(position=impl.position(), angle=impl.heading(), pen=impl.pen().get('pendown')) def left(angle: int, impl: turtle.Turtle, state: TurtleState): # TMP HACK angle = int(angle) impl.left(angle) # return new state (it is immutable) return impl, TurtleState(position=impl.position(), angle=impl.heading(), pen=impl.pen().get('pendown')) def penup(impl: turtle.Turtle, state: TurtleState): impl.penup() # return new state (it is immutable) return impl, TurtleState(position=impl.position(), angle=impl.heading(), pen=impl.pen().get('pendown')) def pendown(impl: turtle.Turtle, state: TurtleState): impl.pendown() # return new state (it is immutable) return impl, TurtleState(position=impl.position(), angle=impl.heading(), pen=impl.pen().get('pendown'))