Skip to content

Instantly share code, notes, and snippets.

@fanf
Created August 14, 2016 23:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fanf/845273a15377347a04375d51a0139b78 to your computer and use it in GitHub Desktop.
Save fanf/845273a15377347a04375d51a0139b78 to your computer and use it in GitHub Desktop.
object Test {
/*
* This is a clearer version of https://gist.github.com/fanf/f26eee67ae33bf54e363e4e5dce01388
*/
type Maybe[A] = Xor[String, A]
final case class Query(value: String)
final case class Result(value: String)
final case class Miss(value: String)
sealed trait MayFail[A]
final object MayFail {
final case class Parse(query: String) extends MayFail[Maybe[Query]]
final case class Get(query: Query) extends MayFail[Maybe[List[Either[Miss, Result]]]]
}
import cats.std.all._
import cats.free.Free
import freek._
type P = MayFail :|: FXNil //yeah, talk me again about free monads for such domain... :)
val P = Program[P]
/*
* Now, I want a method "search" that returns a Maybe[List[Either[Miss,Result]]]
*
* In a "standard" scala program, it would be:
* for {
* query <- MyParser.parse(input) // Maybe[Query]
* results <- MyBackend.get(query) // Maybe[List[Either[Miss,Result]]]
* } yield {
* results
* }
*
* But there is no good way in Freek to differentiate between
* data types that should be in the stack and data types that
* just happen to be sufficiently traversable.
*
* So, the general problem is to be able to tell Freek in some
* way "ignore the N last traversable types, they are actual
* opaque containers for that line.
*
* Typically, in a return type: Xor[String, Option[List[Xor[BusinessTypeA, BusinessTypeB]]]]
* I want to be able to tell: Freek, the stack is just about dealing with
* Xor[String, Option], the List[Xor] is opaque for you and should be
* manipulated like an Int would be.
*
*/
/*
* Working solution: this is the stack I must declare, and only
* to be able to say that I don't want to consider List at all.
* It works, but it is really too convoluted and boilerplaty
* to be a general solution. In a program with 3x that number of
* lines and more complex types/data, it is adding a big price.
*
* That said, I'm not even sure to understand why I don't have to add
* "Either" in the stack to remove it as it is done for List.
*/
type WORKS = Maybe :&: List :&: Bulb
def search1(input: String) = {
for {
lquery <- MayFail.Parse(input).freek[P].onionT[WORKS].peelRight
query = lquery.head // my linter will go mad here
results <- MayFail.Get(query).freek[P].onionT[WORKS].peelRight
} yield {
results
}
}
// this is the actual stack I would like to have:
// "Maybe" is the only type to combine here
type WANTED = Maybe :&: Bulb
/*
* The first idea is to only take the first element of
* the stack, but here, result is of type:
* Maybe[List[Either]], where I would like it to be:
* List[Either].
*/
def search2(input: String) = {
for {
query <- MayFail.Parse(input).freek[P].onionT[WANTED]
result <- MayFail.Get(query).freek[P].onionP[WANTED]
} yield {
result
}
}
/*
* I would like something like belove, where
* freeko1 tells "look for one layer depth",
* freeko2 tells "look for two layers depth",
* etc.
* And freeko tell as today: go all layers.
*/
def search3(input: String) = {
for {
query <- MayFail.Parse(input).freeko[P,WANTED]
/*
* Here, I really wanted to put "freeko1" given
* the above logic, but it seems to be the same as
* onionP and we saw that opionP keep a layer of
* Maybe that should be removed.
*
* So clearly I'm mixing up the "return" type
* of Get and the stack of transformer. Because
* there is a Maybe[Maybe[...]] that must be
* transformed.
*
* So, it points that whatever the solution, user will
* likelly spend some time trying freeko"N +/- 2", with
* N "a good feeling about how layered is my onion".
*/
result <- MayFail.Get(query).freeko2[P,WANTED] //of course does not compile
} yield {
result
}
}
}
@mandubian
Copy link

The problem is naturally a question of order of implicits and implicit conversions:

WORKS = Maybe :&: List :&: Bulb

That works without notifying Either because I have certainly an implicit conversion that accepts F[A] and builds the Onion on it...

but it fails if you want this...

WANTED = Maybe :&: Bulb

But freekoN based on Nat could be possible really ;)

But if you want to have List[Either] instead of Maybe[List[Either]] as a result, it means you are moving Maybe from the result stack into the DSL stack and then it means your DSL Maybe has to become something effectful when interpreted... and then the semantics is a bit different...

Actually, it's a really interesting point that I'm still wondering about and that I should evoke in scala.io if I get the talk and have time in the talk...

@fanf
Copy link
Author

fanf commented Aug 17, 2016

As discussed on twitter (https://twitter.com/mandubian/status/765218945309798400), @mandubian is thrilled to code a OnionN[N <: Nat] for the next version of Freek :))

@fanf
Copy link
Author

fanf commented Aug 17, 2016

(ok, my interpretation of the thread ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment