Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Sep 05 2019 14:43
    @typelevel-bot banned @jdegoes
  • Jan 31 2019 21:17
    codecov-io commented #484
  • Jan 31 2019 21:08
    scala-steward opened #484
  • Jan 31 2019 18:19
    andywhite37 commented #189
  • Jan 31 2019 02:41
    kamilongus starred typelevel/cats-effect
  • Jan 30 2019 00:01
    codecov-io commented #483
  • Jan 29 2019 23:51
    deniszjukow opened #483
  • Jan 29 2019 23:37
  • Jan 29 2019 23:22
  • Jan 29 2019 20:26
    Rui-L starred typelevel/cats-effect
  • Jan 29 2019 18:01
    jdegoes commented #480
  • Jan 29 2019 17:04
    thomaav starred typelevel/cats-effect
  • Jan 28 2019 17:43
    asachdeva starred typelevel/cats-effect
  • Jan 28 2019 07:12
    alexandru commented #480
  • Jan 28 2019 05:45
    codecov-io commented #482
  • Jan 28 2019 05:35
    daron666 opened #482
  • Jan 27 2019 13:56
    codecov-io commented #481
  • Jan 27 2019 13:46
    lrodero opened #481
  • Jan 27 2019 05:47
    codecov-io commented #460
  • Jan 27 2019 05:37
    codecov-io commented #460
PhillHenry
@PhillHenry
Formally.
Fabio Labella
@SystemFw
yeah I don't mean a formal one
the general idea is that you're fully abstract with respect to type parameters, i.e. you have to do the same thing for all instances of them
PhillHenry
@PhillHenry
Yes, that's simple.
Fabio Labella
@SystemFw
say for example def identity[A](a: A): A = a
without cheating (asInstanceOf, nontermination, etc)
parametricity restricts the state space of possible implementations, and it's a big reason for the "if it compiles it works" meme (which obviously isn't strictly true, but it hints at the reduction of the solution space that types give you)
so, parametricity is a good thing
PhillHenry
@PhillHenry
Agreed.
Fabio Labella
@SystemFw
but that means that you can't do, if you care about it, this thing
def identity[A](a: A): A = 
  if (a.isInstanceOf[Int]) (a.asInstanceOf[Int] + 1).asInstanceOf[A] else a
does that also make sense?
as to why you don't want to do that?
PhillHenry
@PhillHenry
Crystal is opaque by comparison.
(Yes)
Fabio Labella
@SystemFw
if (a.isInt) a + 1 (with automatic casting)
is not the the code is dirty, is that now identity suffers from all the problems of identity: Int => Int
i.e the types stopped helping you in restricting the solution space
anyway, that is not desirable
does that make sense?
PhillHenry
@PhillHenry
Absolutely no issues so far.
Fabio Labella
@SystemFw
ok, so let's try building a super dumb IO
without even datatype and interpreter
and no exception handling either (let alone concurrency and all the things that real IO has)
just a referentially transparent representation of synchronous side effects
PhillHenry
@PhillHenry
OK

I guess what I am struggling with is if map is defined as:

map[A, B]: IO[A] => (A => B) => IO[B]

and flatMap as

flatMap[A, B]: IO[A] => (A => F[B]) => IO[B]

if they both result in IO[B] then why does one necessarily run a program and one does not?

Fabio Labella
@SystemFw
you need to look at the type of the function being passed in
also, you have to have IO in A => IO[B] in flatMap, not F
PhillHenry
@PhillHenry
Typo, sorry.
Fabio Labella
@SystemFw
cool, imagine you have this structure
class IO[A](...) {
  def map[B](f: A => B): IO[B]
  def flatMap[B](f: A => IO[B]): IO[B]
}
object IO {
   def delay(a: => A): IO[A] 
}
which is the most corners you can cut in terms of feature, and also does not distinguish between pureand delay (picks delay, which is the most powerful)
let me know if that makes sense so we can go about implementing that, and you'll see why map fundamentally cannot do what you want
(assuming you stick to the principle that all effects are suitably wrapped in IO)
PhillHenry
@PhillHenry
Yup, all straightforward so far.
Fabio Labella
@SystemFw
ok, so the minimal feature that we need to ensure is referential transparency, so we need to suspend execution of the effects that produce A
PhillHenry
@PhillHenry
OK.
Fabio Labella
@SystemFw
the simplest way to do it, with our feature set, is just representing IO internally as () => A
class IO[A](val unsafeRun: () => A) {
  def map[B](f: A => B): IO[B]
  def flatMap[B](f: A => IO[B]): IO[B]
}
object IO {
   def apply(a: => A): IO[A] = new IO(() => a)
}
PhillHenry
@PhillHenry
With you.
Fabio Labella
@SystemFw
ok, so let's implement flatMap now: we need to create a new IO, whose function will run the current IO, pass it to the continuation A => IO[B], and run that
it all follows from the types more or less (with some care to make sure we keep things suspended, referential transparency doesn't hold here, we are implementing it)
class IO[A](val unsafeRun: () => A) {
  def map[B](f: A => B): IO[B]
  def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { () =>
      val myResult = unsafeRun()
      val nextIO = f(myResult)
      nextIO.unsafeRun()
   }
}
object IO {
   def apply(a: => A): IO[A] = new IO(() => a)
}
how does that look?
PhillHenry
@PhillHenry
Yes, read similar examples in a number of FP books. Happy so far.
Fabio Labella
@SystemFw
ok, so now let's implement map
again, from the types it follows that we need to create a new IO, whose suspended function runs the current one, then transforms the result with f
class IO[A](val unsafeRun: () => A) {
  def map[B](f: A => B): IO[B] = new IO[B] { () =>
      val myResult = unsafeRun()
      val result = f(myResult)
      result
  }
  def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { () =>
      val myResult = unsafeRun()
      val nextIO = f(myResult)
      nextIO.unsafeRun()
   }
}
object IO {
   def apply(a: => A): IO[A] = new IO(() => a)
}
right?
PhillHenry
@PhillHenry
Simples.