Higher Kinded Types (Monads, Functors, etc.)

I think this way of emulation does not always work e.g. not as expressive as HKT.
Just from my humble and shallow understanding of FP and CT.

When I used your Swift HKT emulation idea in re-writing Haskell Parsec in Swift following ideas from Parsec Paper.
I encounter a difficulty to implement the following function

runParsecT :: Monad m => ParsecT s u m a -> State s u -> m (Consumed (m (Reply s u a)))

into

public func runParsecT <S, U, A, M:Monad>
  (_ p: Parser<S, U, A>, _ s: ParserState<S, U>, _ dummy: M) ->
  HKT_TypeParameter_Binder<M.HKTValueKeeper, ParserResult<HKT_TypeParameter_Binder<M.HKTValueKeeper, Reply<S, U, A>>>> {
    switch p.unParser(s) {
    case .consumed(let reply):
      switch reply {
      case let .ok(x, s_, e):
        let ok: Reply<S, U, A> = Reply<S, U, A>.ok(x, s_, e)
        
        // mok: HKT_TypeParameter_Binder<M.HKTValueKeeper, M.A>
        // M.A == Reply<S, U, A>
        let mok = M.return(ok as! M.A)
        
        // cok : ParserResult<HKT_TypeParameter_Binder<M.HKTValueKeeper, M.A>>
        let cok = ParserResult.consumed(mok)
        
        // mcok: HKT_TypeParameter_Binder<M.HKTValueKeeper, M.A>
        // M.A = ParserResult<HKT_TypeParameter_Binder<M.HKTValueKeeper, M.A>>
        let mcok = M.return(cok as! M.A)
        
        // Error: Cannot convert
        // HKT_TypeParameter_Binder<M.HKTValueKeeper, M.A>
        // into
        // HKT_TypeParameter_Binder<M.HKTValueKeeper, ParserResult<HKT_TypeParameter_Binder<M.HKTValueKeeper, Reply<S, U, A>>>>
        return mcok
      case let .error(e): fatalError("left to implement")
      }
    case .empty(let reply): fatalError("left to implement")
    }
}

As what I see, The M.A (the wrapped type in monad) cannot be two different type.
Because in Swift, every generic type should be deducible to a concrete type. Then when parsing this function, the type for M should be a concrete type, therefore M.HKTValueKeeper and M.A are concrete types as well.
So M.A cannot represent different types in the same context.

Is there something wrong in my reasoning. Please let me know.
By the way, actually, I love FP and CT, really looking forward that Swift can fully support FP and CT sooner or latter.

1 Like

@KeithTsui is there a repo you can link to? This isn’t quite enough context to help you out.

@anandabits
Thanks for you reply.
Here is link to the file in my personal Repo.
RunParsec in Swift
If there is something unclear, please let me know.
I'd appreciate any information you could give me.

1 Like

Very interesting, thanks for sharing @KeithTsui. I already saw multiple implementations of abstractions with use of HKT in Swift. Multiple people trying to do the same:

My pleasure. @Nikita_Leonov
I am quite obsessed by the way of thinking in FP and CT. It is neat and elegant, highly emphasizes on abstraction and composition.
Your idea to make a common library for HKT container is very cool. If with some practical uses in real work, that would be better. It may help to clear some blur concepts about HKT or FP in swift. Furthermore, it may help to understand deeply about type theory. e.g. What would be the boundary of a type system, what can be done within that boundary, and how to expend the boundary by adding new feature to that type system. So interesting I feel when imagine a bit. But one thing at a time.
I would like to dive deeper into it as well if I am capable to. :joy:
Thanks for your idea. May some talents make those pavers as well.

The shape and appearance of Swift code using HKT emulation is generally ugly and hard to read, and doesn't really improve maintainability of production code.

You can really see this after fiddling for a while with languages like Haskell or Purescript: the syntax in these languages is very clean and perfectly appropriate for representing higher-lever abstractions in a concise and expressive way. In its current state, Swift is not adequate for that kind of code.

A proper way for abstracting over the kind (even just * -> * -> *) would push the expressiveness of Swift to new levels, but for now the syntax is simply not here.

A more general note. As it's pretty clear in the Purescript community, the ability to abstract over the kind is not just useful for releasing cool "magical" libraries (as it would be, with a lot of effort, even with HKT emulation): it's also useful to unlock a whole new way to reason about code and algorithms, and there's a gigantic amount of academic literature and community discussion on blog articles, or even Twitter, about new and exciting ways to represent higher abstractions, that lead to the discovery of interesting isomorphisms between different representations (think about all the work done on functional Optics), something that it's simply not there in other communities, like the OO one.

These kinds of discussions are happening right now, and their results and outcomes will become more and more relevant over the years: we've seen the first "wave" of functional concepts (higher-order functions, lambdas and such) in non-functional languages, and we're going to see more; this process is not going to stop. If Swift hopes to be relevant in 10 to 20 years, the possibility for higher abstraction has to come, eventually, and the sooner the better. And I imagine that the appreciation of the Swift core team and community of code conciseness and expressiveness, but also accessibility, will bring something new and interesting to the table, but in my opinion this machine should start moving as soon as possible.

13 Likes

What does this even mean?

1 Like

That's a signature for a type constructor, that is, a function that constructs a type.

For example, a signature like * means constant type, like for example Int or String.

* -> * means give me a type, and I'l give you a type, for example Array<Element> is a type constructor of this kind, because if I give it a type (say, Int), it gives me back a specific type Array<Int>.

* -> * -> * means give me a type, and I'll give you a type constructor to which you give a type to get a type: a generic type with a single generic parameter is abstractly of this kind. To represent this in a Swifty way I could use a signature like A<B>, where both A and B are generic: this way I'm expressing the idea that A is a generic type (any generic type, it could be Array or Optional, for example) with a generic parameter, that I'm calling B.

A basic application of this would be in the protocol definition of Functor. If something is a functor, it must have a map method that returns an instance of the same base type but (in general) a different specialization for the generic type. If we could express the kind in Swift, a functor protocol could look like this:

protocol Functor {
    kind Self<Element>
    func map <Other> (_ transform: (Element) -> Other) -> Self<Other>
}

Any concrete generic type conforming to this protocol would automatically get the kind associated type equal to itself (Element would be the first generic parameter if there's more than one). But that's just an idea to explain how it would work.

Anyway, Self<Element> would mean that the kind is * -> * -> *, because both Self and Element are generic, and both Optional<Wrapped> and Array<Element> would fit in this kind.

6 Likes

You are correct that M.A is already constrained to a concrete type here. You would have to break it up and refer to everything in terms of HKT_TypeParameter_Binder with the same HKTValueKeeper.

You have M<ParserResult<M<Reply<S, U, A>>>>, and M<Reply<S,U,A>>> (inside the first one). The plain M<A> is not needed since it isn't used anywhere. You could do something like the following (have not checked this actually type checks, but should be close):

public func runParsecT <S, U, A, MValueKeeper> (
    _ p: Parser<S, U, A>,
    _ s: ParserState<S, U>
) -> HKT_TypeParameter_Binder<MValueKeeper, ParserResult<HKT_TypeParameter_Binder<MValueKeeper, Reply<S, U, A>>>>

Since everything is present in the signature, you can actually take this a step further and make the output types fully formed. You will lose some type inference for this, but it isn't any worse than having to directly call extractValue for the correct type afterward. Depends on if you intend to continue working in an HKT context and if the mental overhead of HKT_TypeParameter_Bind outweighs computational overhead of functions that just cast back and forth (not sure how much of this can be optimized away by the compiler).

public func runParsecT <S, U, A, M1: Monad, M2: Monad> (
    _ p: Parser<S, U, A>,
    _ s: ParserState<S, U>
) -> M1
  where
    M1.A == ParserResult<M2>,
    M2.A == Reply<S, U, A>,
    M2.HKTValueKeeper == M1.HKTValueKeeper // This is what keeps `m` the same
1 Like

Thanks for jumping in with a response here @r-peck. I have been planning to take a look at this and just haven't had a chance yet. :)

I think there are several of us working with the HKT encoding and we're starting to see some open source code published. In addition to the links above, there is GitHub - inamiy/HigherKindSwift: An experimental Higher Kinded Types in Swift.. I expect a period of experimentation with different approaches to working with the encoding. If native HKT is not added to Swift I would expect that to be followed by a period of consolidation as people interested in HKT converge on one library / tool (or a small number of competing libraries / tools).

I don't think anybody intends to write code directly in the encoding. In order to gain traction it needs to be encapsulated in a library and / or code generator.

Of course all of us interested in HKT would prefer to have them directly supported in the language. In fact, establishing concrete, demonstrated use cases for HKT in Swift is one of the best approaches to motivating their addition to the language. That is why I have been doing what I can to make people aware of the encoding and encourage them to experiment with it.

3 Likes

This is exactly the mindset I've been moving forward with. Seeing practical examples of this in use is going to be more motivating toward language support than the abstract concepts it enables isolated from any application. I'm really looking forward to what people are able to accomplish even with the sharp edges of the encoded approach.

1 Like

Thx for trying to explain but you lost me at functor.

But he defined it for you. A functor is a type with an associated type Element with a method map that takes a closure of (Element) -> (T) and returns Self<T>. For example: Array & Optional are both functors.

Well different background. For me his little code fragment and your explanation look like Hieroglyphics. Not something clicking in place with my brain.

Okay, imagine this abstract class:

public class AbstractFunctor<T> {
    // Must return Self<U>
    public func map<U>(_ transform: (T) -> U) -> AbstactFunctor<U> {
        fatalError("You must override this method")
    }
}

What we have been describing to you is this class as a protocol that can be applied to any kind of type (structs, enums, classes, etc.)

The important part here, is that we currently can't specify the Self<U> requirement in Swift. Even though that requirement is useful for lots of things; not just for functional stuff.

1 Like

I may be to stupid for fp. This class doesn't look any less cryptic. perhaps it's the very, very high amount of generics in it? Never got beyond generic lists before they start to make zero sense to me...

Okay, let's try something a little less abstract. Array<Element> is a collection of Elements. If you have an Array<Int> then Element is Int. So, if you call map on an Array<Int> then you need to give it a function that converts an Int to some other type. It then produces an array of that type containing each of the values of the first array converted into the new type. For instance [1, 2, 3].map { String($0) } will give you ["1", "2", "3"].

Optional either holds a value of Wrapped or it holds nothing. So, an Optional<Int> holds either an Int or nothing. If you call map on Optional<Int> then you must also give it a function that converts a value of Int into some other type and it'll return an Optional that can hold that type. So, Optional(1).map { String($0) } returns Optional("1") See the pattern?

A functor is any type that has that same map method as Array and Optional. Note: Set is not a functor because it's map doesn't return another Set.

4 Likes

Like Pinkamena, FP doesn't really click with me. However, this explanation of functors is perfect! IMO :slight_smile:

2 Likes

Very important to understand we are actually talking about discrete mathematics and higher algebra under the hood. Functional programming as a paradigm and a branch of discrete mathematics itself simply makes use of those fields in computer science.

The bottom line is, in terms of understanding, it's most likely the math involved rather than FP that doesn't click with one. If I'm wrong, a good idea would be to look up the mathematical definition of a functor; it might help in revealing the correlation between the shared code and your understanding of mathematics.

Thanks @r-peck @anandabits for helping me out, I really appreciate your efforts.
@anthonylatsis Here is my humble understanding. Overall, I agree with you that Programming and Mathematics are really closely related.
Their relationship is somehow revealed by Curry–Howard–Lambek correspondence and Curry–Howard correspondence
Moreover, in Elements of Programming, the author said, he thought Mathematics and Programming is literally the same discipline.
Furthermore, in Church–Turing thesis, it states that lambda-calculus is equivalent to Turing Machine.
And FP as you mentioned is kind of paradigm, and the theory behind that is about lambda-calculus, as we can see in Haskell with GHC complier, the System F is backbone of its type system. GHC, is a fantastic example depicting the link between lambda-calculus and programming.
Therefore, learning maths would deeper our understanding of programming.
(Excuse my shallow understanding about them :relaxed:)