Category Theory for Programmers

Kleisli Categories

Kleisi Categories

Composition and Identity on Embellished Types

func atoi(s string) (int64, error) {
    i64, err := strconv.ParseInt(s, 10, 64)
    if err != nil {
        return 0, err
    }
    return i64, nil
}
func unmarshal(data []byte) (string, error) {
    var s string
    err := json.Unmarshal(data, &s)
    if err != nil {
        return "", err
    }
    return s, nil
}

func atoi(s string) (int64, error) {
    i64, err := strconv.ParseInt(s, 10, 64)
    return i64, err
}
func unmarshal(data []byte) (s string, err error) {
    err = json.Unmarshal(data, &s)
    return
}

func atoi(s string) (int64, error) {
    i64, err := strconv.ParseInt(s, 10, 64)
    return i64, err
}

func toNum(data []byte) (int64, error) {
    s, err := unmarshal(data)
    if err != nil {
        return err
    }
    return atoi(s)
}
func toNum(data []byte) (int64, error) {
    s, err := unmarshal(data)
    if err != nil {
        return 0, err
    }
    i, err := atoi(s)
    if err != nil {
        return 0, err
    }
    return i, nil
}

func unmarshal(data []byte) (s string, err error) {
    err = json.Unmarshal(data, &s)
    return
}

func atoi(s string) (int64, error) {
    i64, err := strconv.ParseInt(s, 10, 64)
    return i64, err
}
// type ValueOrError = (A, error)

func toNum(data []byte) (int64, error) {
    s, err := unmarshal(data)
    if err != nil {
        return 0, err
    }
    i, err := atoi(s)
    if err != nil {
        return 0, err
    }
    return i, nil
}

func unmarshal(data []byte) (s string, err error) {
    err = json.Unmarshal(data, &s)
    return
}

func atoi(s string) (int64, error) {
    i64, err := strconv.ParseInt(s, 10, 64)
    return i64, err
}
// type ValueOrError = (T, error)

type A = []byte
type B = string
type C = int64
type F = func(A) (B, error)
type G = func(B) (C, error)
type GdotF = func(A) (C, error)

func deriveCompose(f F, g G) GdotF {
    return func(a A) (C, error) {
        b, err := f(a)
        if err != nil {
            return 0, err
        }
        c, err := g(b)
        if err != nil {
            return 0, err
        }
        return c, nil
    }
}

func toNum(data []byte) (int64, error) {
    return deriveCompose(unmarshal, atoi)(data)
}

Error Category Composition

Error Category Identity

func Identity(value T) (T, error) {
    return value, nil
}

Kleisli Error Handling

func upgradeUser(endpoint, username string) error {
    getEndpoint := fmt.Sprintf("%s/oldusers/%s", endpoint, username)
    postEndpoint := fmt.Sprintf("%s/newusers/%s", endpoint, username)

    resp, err := http.Get(genEndpoint)
    if err != nil {
        return err
    }
    data, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }
    olduser, err := user.NewFromJson(data)
    if err != nil {
        return err
    }
    newuser, err := user.NewUserFromUser(olduser),
    if err != nil {
        return err
    }
    buf, err := json.Marshal(newuser)
    if err != nil {
        return err
    }
    _, err = http.Post(
        postEndpoint, 
        "application/json", 
        bytes.NewBuffer(buf),
    )
    return err
}

Kleisli Error Handling

func upgradeUser(endpoint, username string) error {
    getEndpoint := fmt.Sprintf("%s/oldusers/%s", endpoint, username)
    postEndpoint := fmt.Sprintf("%s/newusers/%s", endpoint, username)

    _, err := deriveCompose(
        http.Get,
        func(resp *http.Response) ([]byte, error) {
            return ioutil.ReadAll(resp.Body)
        },
        newUserFromJson,
        newUserFromUser,
        json.Marshal,
        func(buf []byte) (*http.Response, error) {
            return http.Post(
                postEndpoint,
                "application/json",
                bytes.NewBuffer(buf),
            )
        },
    )(getEndpoint)
    return err
}

Monads

(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)

(>=>) f g = \a -> f a >>= g

(>>=) :: m b -> (b -> m c) -> m c

(>>=) = join . fmap

fmap :: (a -> b) -> f a -> f b

fmap :: (b -> m c) -> m b -> m m c

join :: m m c -> m c

A function that is not defined for all possible values of its argument is called a partial function. It’s not really a function in the mathematical sense, so it doesn’t fit the standard categorical mold. It can, however, be represented by a function that returns an embellished type optional:

template<class A> class optional {
    bool _isValid;
    A    _value;
public:
    optional()    : _isValid(false) {}
    optional(A v) : _isValid(true), _value(v) {}
    bool isValid() const { return _isValid; }
    A value() const { return _value; }
};
optional<double> safe_root(double x) {
    if (x >= 0) return optional<double>{sqrt(x)};
    else return optional<double>{};
}

As an example, here’s the implementation of the embellished function safe_root:

  1. Construct the Kleisli category for partial functions
    (define composition and identity).
template<class A, class B, class C> 
    function<optional<C>(A)> compose(
        function<optional<B>(A)> f, 
        function<optional<C>(B)> g) {
    return [f, g](A a) {
        auto b = f(a);
        if (!b.isValid()) {
            return <optional><C>{};
        }
        auto c = g(b.value());
        return c;
    };
}

template<class A> optional<A> identity(A) {
    return optional<A>{x};
}

2. Implement the embellished function `safe_reciprocal` that returns a valid reciprocal of its argument, if it’s different from zero.

optional<double> safe_reciprocal(double x) {
    if (x != 0) return optional<double>{1 / x};
    else return optional<double>{};
}

3. Compose `safe_root` and `safe_reciprocal` to implement `safe_root_reciprocal` that calculates sqrt(1/x) whenever possible.

optional<double> safe_root_reciprocal(double x) {
    return compose(safe_reciprocal, safe_root)(x);
}

category-theory-for-programmers-104

By Walter Schulze

category-theory-for-programmers-104

  • 1,063