Higher Kinded Types (Monads, Functors, etc.)

The morphisms in the category of Swift types are functions. In the category of Swift types, types are objects (like A, B, () and Never) and arrows are functions (like (A) -> B, () -> A, (B) -> Never) Here's why Optional is a (endo)functor in the category of Swift types:

Optional maps any type (an object in the category of Swift types) to another type, and any function between two types (an arrow in such category) to another function.

For example, Optional.init maps A to Optional<A>, and Optional.map maps (A) -> B to (Optional<A>) -> Optional<B>.

I don't know about all this FP terminology, but isn't that Optional.flatMap?

(or maybe Optional.compactMap after the renaming)

2 Likes

The standard library is inextricably tied to the type system. If we added HKT, should we add a standard Functor type? Should the appropriate types conform to Functor? What would the trade-offs be?

Whoa, I didn't see this. Of course Optional is a monad, and chaining is achieved via flatMap. Don't get me wrong, but are you sure about your knowledge of the Swift standard library? :smiley:

I don't know the answer to your questions: I would say "no, without adding higher-order utilities like the free monad or a Traversable protocol".

But these are completely different questions in respect to "should we add the possibility of HKTs to the Swift type system".

Not too familiar with FP concepts - what kinds of generic algorithms would a Functor protocol enable?

So the set of morphisms for types A and B are all the possible (A) -> B, and that set can be empty if A != B. Otherwise, it has to at least be of cardinality 1 due to the requirement of having an identity morphism (A) -> A for every object.

Since id A has to exist, how is it represented in Swift? NSObject's copy? Though that doesn't count for protocols..

Superficially it makes sense, but there are some nuances.

Since the objects are types, it should be (A.Type) -> B.Type for pair (A.self, B.self), which changes things quite a bit. Shouldn't it be a category of instances rather than types?

Optional<T> as a type would be then a monad from the category of non-optional instances to the category optional instances.

Not considering the issues above, it is pretty clear now, thank you.

Ah! I'm glad you noticed that. I did not take flatMap into account.

Of course not. The Standard Library is vast and has many areas I do not know much about. That's precisely why I ask questions.

1 Like

Is the discussion here possible without this amount of fp lingo no one outside the fp circle understands? - Not everybody is a math major in theoretical mathematics…

id A, being an arrow in the category, is represented via a function, in this case the identity function, that is:

func identity <A> (_ value: A) -> A {
   return value
}

No, it's the category of types, and an arrow is a function that takes instances of the source type and returns instances of the target type, or in other words, maps the source type to the target type, in the same way that for the category of sets arrows are (total) functions between sets, that take elements of the source set and return elements of the target set. Don't be confused by the fact that the objects in category of Swift types are types: arrows are functions from a type to another, which means that they take instances of a type in input and return instances of another type in output.

I'm not a fan of jargon, but unfortunately it would be really hard to not use any jargon at all, but I can help with specific instances if you want.

To summarize the chain of reasoning up to this point:

  • I'd like to have HKTs in Swift;
  • We need concrete examples of their usefulness;
  • Functors and Monads are concrete examples;
  • Functors and Monads come from category theory;
  • Let's talk category theory;

I'd gladly avoid talking about CT here (in the present context it's not useful at all), I just needed to answer some direct points.

Yes, of course you're right, my bad. Thank you for clarifying everything else!

Could someone explain this? I see the value of having a term for these concepts, but how would being able to model these concepts as protocols be practically useful? Genuinely curious.

Monads can be used to model effectful computations. Abstracting over the monad used to interpret the computation can be extremely useful. The most obvious and immediate example is to substitute a mock interpreter used by tests.

This is a really deep topic. If you're interested in learning more I recommend looking at what folks in the Scala and Kotlin communities are doing. These communities have a more fully developed ecosystem for working with these abstractions than we do in the Swift community. A good place to start might be this video: Functional approach to Android architecture using Kotlin - Jorge Castillo - YouTube.

6 Likes

So what is effectful computations? Even google only list some obscure hits. Any short and easy explanation that we non math majors have a chance to understand what the feature is about?

A nice simple example is modeling the side effects of an interactive console application. The computation can write to the console and request input from the user. There is a brief overview of the concrete benefits of this approach in the talk Beyond Free Monads given by John de Goes at a Scala conference last year.

If you're really interested in learning about these techniques I suggest you take a look at this and the prior You Tube link I shared. It will take more than a few minutes but maybe you will find it worth the time you invest.

Well at least I have a splitting headache again but still don't see why not simply use a normal io function/method. What I guess from it is that effectful computations means "has sideeffects". See that was an easy explanation that many developers will understand. Why make it more complicated by packing it into a new name?

It's more general than that, but side effects are a useful subset that is pretty straightforward to think about.

Here's an example related to something that I use pretty frequently in my code.

Assume you have an Array<String> that you want to transform into an Array<Int>; you have a function transform: (String) -> Result<SomeError,Int> because you need to be precise about the cases where a String cannot be transformed into an Int.

If you map your array you end up with an Array<Result<SomeError,Int>> but you would really like this to be the other way around, that is, Result<SomeError,Array<Int>>, so that the transformation results in a single Result with an eventual error representing the first error found, or more informatively all the errors produced within the original array.

It turns out that you can do this generically because Result is an applicative functor, that is, it provides a way to construct a result with a single value, and a way to apply a function to a result when the function is itself contained into a result. Thus, the only thing that you require from result is a .pure static method, and an "apply" operator <*>.

Now, suppose you have a Future<A> type: a future is an applicative functor too (actually, in more than one way), so if your function was a transform: (String) -> Future<Int> you could use the exact same method to get a Future<Array<Int>>.

That's because that method (let's call it traverse) requires a function of type (Element) -> T where T is an applicative functor, but this can't be represented in Swift, so you need to write a lot of specialized functions with the same shape and only different names for the concrete types.

You can find some more examples in a library I wrote that adds a bunch of functional constructs to Swift, but in a Swifty way (in the sense that favors methods over free functions and operators): can I link that here?

6 Likes

So by ‘effectful’ here, you mean computations with IO. You model these so everything is performed lazily, with a type modelling each kind of operation. Things end up naturally composing lazily, similar to the Collection.lazy view's methods.

What I (hopefully) understood from the video, is that these concepts being protocols allows extensions to flatten the related API, removing the need to unwrap each level when they're nested. A concrete example:

extension Optional where Wrapped: Optional {
    func map<B>(_ transform: (Wrapped) -> B) -> Optional<B> {
        switch self {
        case .some(let wrapped): return wrapped.map(transform)
        case .none: return .none
        }
    }
}

When this kind of thing is automatic, you stop having to worry about the huge amounts of generic nesting you get with functional programming, and can unwrap however many levels in one go. Is that the gist of it?

I would say that "side effect" means "modifying state outside a local scope", like for example mutating an object passed as input, or a global variable. "Effectful" in general means this but also more, like reading from a global mutable state, or passing some state around from context to context. Monads are a tool to model these kinds of things, in a way that turns impure functions into pure ones, and allows better and easier reasoning about code (assuming that one knows what those monads mean, of course).

A very simple example is an if statement over a "nullable" value: if I check for the value's existence, and enter a branch of my if, there's literally no other way for me to do something actually useful in my program than mutating some external variable in the outer scope. That's why Optional exist: I can model the idea that a value exists or not, and keep working like it did, maintaining purity in functions.