arrow-kt / arrow-core

Λrrow Core is part of Λrrow, a functional companion to Kotlin's Standard Library
http://arrow-kt.io
Other
81 stars 30 forks source link

["Request"] Indexed monad! #282

Closed kylegoetz closed 3 years ago

kylegoetz commented 3 years ago

Lately I've been doing some work in TypeScript, and there is a FP library for writing web servers called hyper-ts that uses an indexed monad to ensure a route handler follows a prescribed state progression: write a response status, then write or skip writing headers, then write the body and send.

It is a compile-time error to write a body and then write headers, or write headers/body before writing status, or to skip writing status.

Some utility functions (like json) will write an 'application/json' header, then mark headers as done being written, then write a stringified response.

I added my own indexes to let you define a controller as requiring authentication and authorization and require these steps to be performed before writing status.

Basically an indexed monad is defined as Monad InitialState NewState value (with the state being the "index" in "indexed monad").

You can define a function (pseudocode) like setStatus: (statusValue:number) => Monad StatusOpen HeadersOpen void and setHeader: (name:string,value:string) => Monad unknown HeadersOpen void => Monad HeadersOpen HeadersOpen void and closeHeaders: () => Monad unknown HeadersOpen void => Monad HeadersOpen BodyOpen void and so forth. You compose these and will not be able to use setHeader after closeHeaders because setHeader expects the initial index to be HeadersOpen but closeHeaders sets the index to BodyOpen.

I'm being long-winded. Here is the inspiration for this post. I'm not saying add this Express middleware stuff. Just the actual IMonad type to enable this kind of thing would be cool to see.

nomisRev commented 3 years ago

Hey @kylegoetz,

It's certainly possible to build these kind of things in Kotlin. I've build IndexedStateT a couple years ago, but the Kotlin Compiler could at that time not handle the complexity of the generics 😅 It however still lives in a branch on the repo here.

We're however removing Monad and the kinded typeclasses from Arrow.

I'm however wondering if you could define those things as follows:

fun StatusOpen.setStatus(statusValue: Number): HeadersOpen and fun HeadersOpen.setHeader(name: String, value: String): HeadersOpen and fun HeadersOpen.closeHeaders(): BodyOpen. Which would reuslt in following DSL.

setStatus(200)
  .setHeaders("Accept-Charset", "utf-8")
  .setHeaders("Cache-Control", "no-cache")
  .closeHeaders()
  ...

Since you can easily mix in suspend functionality here, you can automatically mix it with IO based code. Wrapping it inside either { } would also allow you to use ! inside inline fun lambdas in this DSL.

So I think in this way such libraries can be build without having to rely on Monad hierarchies.