-
Notifications
You must be signed in to change notification settings - Fork 39
part-2.4, 'Representable Functors' #51
part-2.4, 'Representable Functors' #51
Conversation
c47f5d3
to
6ba75ec
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
beautiful! Thanks!
There's quite a lot to review on this :) P.S. @Zelenya Thanks a lot for all this effort! 😄 |
``` | ||
```scala | ||
`C(A, X)` => `C(A, Y)` | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above is pseudo haskell, so I'm not sure how to translate it. but it should probably be C[A, X]
. The way you've written that's a specific type which happens to be called C(A, X)
(the A
and X
are part of the name)
f ∘ h :: a -> y | ||
``` | ||
```scala | ||
val `f ∘ h`: A => Y |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scala code is not the same as the haskell above:
in the haskell snippet a
and y
are universally quantified, whereas in the scala code they are concrete types called A
and Y
.
It should be
def `f ∘ h`[A, Y] : A => Y
``` | ||
```tut:silent | ||
case class Reader[A, X](run: A => X) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strictly speaking the haskell code is using a type synonym, not a newtype, so the translation is a also a type synonym, not a case class. Not sure this is worth changing though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've shamefully copy-pasted it from the 1.10 and not noticed the difference.
It was newtype Reader e a = Reader (e -> a)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, I don't really mind too much which way you go, just wanted to point out the discrepancy :)
new Functor[({type T[X] = Reader[A, X]})#T] { | ||
def fmap[X, B](f: X => B)(h: Reader[A, X]) | ||
: Reader[A, B] = | ||
Reader(x => f(h.run(x))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here re: newtype
``` | ||
```tut:silent | ||
case class Op[A, X](f: X => A) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here re: newtype
αx :: C(a, x) -> F x | ||
``` | ||
```scala | ||
val αx: `C(A, X)` => F[X] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A
and X
are type variables in the haskell code, in yours C(A, X)
is a concrete type named C(A, X)
(there the X
has no connection with the latter X
), and X
is a concrete type called X
``` | ||
```scala | ||
def alpha[X]: (A => X) => F[X] | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is incorrect. The haskell version has type
alpha :: forall a. (forall x. (a -> x) -> F x)
so it's a natural transformation between (-> a)
and F
. The scala code is a normal function where X
is a type parameter, F
is fixed (correctly), and A
is fixed (incorrectly). And the returned type is not polymorphic enough (it's not first class polymorphic).
To encode this you need a type such as the one in cats:
trait FunctionK[G[_], H[_]] {
def apply[A](ga: G[A]): H[A]
}
and alpha
becomes
def alpha[A]: FunctionK[A => ?, F]
```scala | ||
fmap(f)(alpha(h)) == alpha(f compose h) | ||
``` | ||
................ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code for the two laws will probably change once you apply the change above
``` | ||
```scala | ||
def beta[X]: F[X] => (A => X) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment re: natural transformation applies to beta
```tut:silent | ||
def alpha[X]: (Int => X) => List[X] = | ||
h => List(12).map(h) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same, this should be
def alpha: FunctionK[Int => ?, List] = ...and so on
``` | ||
```scala | ||
def beta[X]: List[X] => (Int => X) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same:
def beta: FunctionK[List, Int => ?]
h: () => A, | ||
t: () => Stream[A] | ||
) extends Stream[A] | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a general thing about datatypes, but in scala one would never have a trait with only one subclass, just go with
case class Stream[X](h: () => X, t: () => Stream[X])
``` | ||
```tut:silent | ||
trait Representable[F[_]] { | ||
type Rep[F[_]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be lower kinded, type Rep
Type families and abstract types work differently
type Rep[F[_]] | ||
|
||
def tabulate[X](f: Rep[F] => X): F[X] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rep
|
||
def index[X]: F[X] => Rep[F] => X | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rep
else index(bs())(n - 1) | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will become:
implicit def streamRepresentable: Representable[SStream]{type Rep = Int} =
new Representable[SStream] {
type Rep = Int
def tabulate[X](f: Rep => X)
: SStream[X] =
SStream[X](
() => f(0),
() => tabulate(f compose (_ + 1)))
def index[X]
: SStream[X] => Rep => X =
s => n => {
if (n == 0) s.h()
else index(s.t())(n - 1)
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the spam, didn't realise I wasn't doing it all at once in a review
db6c332
to
98fa2e8
Compare
@SystemFw Ready for the round 2. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of small changes left! Thanks a lot for your continued effort :)
αx :: C(a, x) -> F x | ||
``` | ||
```scala | ||
def α[X]: C[A, X] => F[X] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing an A
type parameter
In order to make a universal transformation, | ||
another type needs to be introduced. | ||
FunctionK (~>) transforms values from one | ||
first-order-kinded type into another: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first-order-kinded type
is confusing. Also, the description would also apply to
def foo[F[_], G[_], A](fa: F[A]): G[A]
, which isn't a natural transformation: it's really the higher rank polymorphism that matters here. Personally I'd just remove the second paragraph and link at the docs for FunctionK
index (Cons b bs) n = if n == 0 then b else index bs (n - 1) | ||
``` | ||
```tut:silent | ||
implicit def streamRepresentable = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add the explicit type here, for two reasons:
- it's good practice to always annotate implicits
- it's someone decides to add a signature of
Representable[Stream]
the code will compile, but be broken as the refinement is lost
98fa2e8
to
20d632c
Compare
``` | ||
```tut:silent | ||
implicit def streamRepresentable | ||
: Representable[Stream] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I swear this is the last one 😂
The type of this is Representable[Stream]{type Rep = Int}
, you want to keep the Rep = Int
refinement
20d632c
to
27d5fb3
Compare
27d5fb3
to
a41bd79
Compare
@SystemFw final round? 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉
Thank you so much! Sorry for the picky review
Fixes #22