Created
August 14, 2016 23:40
-
-
Save fanf/845273a15377347a04375d51a0139b78 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
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 :))
(ok, my interpretation of the thread ;)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 acceptsF[A]
and builds the Onion on it...but it fails if you want this...
WANTED = Maybe :&: Bulb
But
freekoN
based onNat
could be possible really ;)But if you want to have
List[Either]
instead ofMaybe[List[Either]]
as a result, it means you are movingMaybe
from the result stack into the DSL stack and then it means your DSLMaybe
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...