Partially applied type constructor in Scala 3? - scala

Reading "Learn You a Haskell for Great Good" and trying to understand how Haskell concepts of the book may be written in Scala 3.
chapter 11 mentions "partially applied type constructors" in section about Functions as Functors:
Another instance of Functor that we've been dealing with all along but didn't know was a Functor is (->) r. You're probably slightly confused now, since what the heck does (->) r mean? The function type r -> a can be rewritten as (->) r a, much like we can write 2 + 3 as (+) 2 3. When we look at it as (->) r a, we can see (->) in a slightly different light, because we see that it's just a type constructor that takes two type parameters, just like Either. But remember, we said that a type constructor has to take exactly one type parameter so that it can be made an instance of Functor. That's why we can't make (->) an instance of Functor, but if we partially apply it to (->) r, it doesn't pose any problems. If the syntax allowed for type constructors to be partially applied with sections (like we can partially apply + by doing (2+), which is the same as (+) 2), you could write (->) r as (r ->)
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
I understand all this logic in the book + the fact that Haskell deals with type constructors just as with usual functions - those can be curried and partially applied.
Question:
What is Scala 3 analogue of such partially applied type constructor so we might define fmap in the way that visually resembles below Haskell definition?
(it is smth that can be modelled with higer-kinded types?)

In Scala 3 you can use type lambdas
trait Functor[F[_]]:
def map[A, B](f: A => B)(fa: F[A]): F[B]
given [R]: Functor[[X] =>> R => X] with
override def map[A, B](f: A => B)(fa: R => A): R => B = fa andThen f
or kind projector (scalacOptions += "-Ykind-projector")
given [R]: Functor[R => *] with
override def map[A, B](f: A => B)(fa: R => A): R => B = fa andThen f
Eventually this should become
given [R]: Functor[R => _] with
override def map[A, B](f: A => B)(fa: R => A): R => B = fa andThen f
Polymorphic method works with type lambda, but not with type wildcard in Scala 3

Related

In Scala cats-laws, why is the functor composition law different from canonical definition?

The (covariant) functor definition in cats-laws looks like this:
def covariantComposition[A, B, C](fa: F[A], f: A => B, g: B => C): IsEq[F[C]] =
fa.map(f).map(g) <-> fa.map(f.andThen(g))
But if I translate the functor composition rule to Scala, it should be:
def covariantComposition[A, B, C](fa: F[A], f: A => B, g: B => C): IsEq[F[C]] =
fa.map(f).andThen(fa.map(g)) <-> fa.map(f.andThen(g))
Why are they different? Which version is correct?
UPDATE 1 I'm aware of a similar implementation in Haskell, but I haven't had a chance to read it. I wonder if the Haskell version is more by the book.
F(g ∘ f) = F(g) ∘ F(f) is the same as ∀fa, (F(g ∘ f))(fa) = (F(g) ∘ F(f))(fa) (equality of functions is equality of images for all arguments, this is extensionality in HoTT 1 2 3).
The latter is translated as
def covariantComposition[A, B, C](fa: F[A], f: A => B, g: B => C): IsEq[F[C]] =
fa.map(f).map(g) <-> fa.map(f.andThen(g))
(actually, fa.map(f.andThen(g)) <-> fa.map(f).map(g)).
If you'd like to have "point-free" F(g ∘ f) = F(g) ∘ F(f) you could write _.map(f.andThen(g)) <-> _.map(f).map(g) or _.map(f.andThen(g)) <-> (_.map(f)).andThen(_.map(g)) (this is fmap (g . f) = fmap g . fmap f in Haskell, or more precisely, in some "meta-Haskell").
The 2nd code snippet in your question
def covariantComposition[A, B, C](fa: F[A], f: A => B, g: B => C): IsEq[F[C]] =
fa.map(f).andThen(fa.map(g)) <-> fa.map(f.andThen(g))
is incorrect. fa.map(f).andThen... doesn't make sense as it was mentioned in comments. You seem to confuse F and F[A].
In category theory, in general categories, f: A -> B can be just arrows, not necessarily functions (e.g. related pairs in a pre-order if a category is this pre-order), so (F(g ∘ f))(fa) can make no sense. But the category of types in Scala (or Haskell) is a category where objects are types and morphisms are functions.
I think your confusion comes from the different way functor map property can be represented.
trait Functor[F[_]] {
def map1[A, B](f: A => B): F[A] => F[B]
def map2[A, B](f: A => B)(fa: F[A]): F[B]
def map3[A, B](fa: F[A])(f: A => B): F[B]
}
Here... map1 is the haskell aligned definition... and hence the functor law representation used by haskell also works with this one.
So, this haskell
fmap (g . f) = fmap g . fmap f
translates to following Scala
map1( g.compose(f) ) = map1(g).compose( map1(f) )
// or
map1( f.andThen(g) ) <-> map1(f).andThen(map1(g))
But, the thing is that we have few more ways to represent the same map property as given by map2 and map3. The overall essens is still the same, we just switched the representation.
Now, when we add the full object oriented angle to it... the "object-oriented" Functor becomes something like following.
trait List[+A] {
def map(f: A => B): List[B]
}
So... for the "object oriented functor" like List, the same law can be represented as following
listA.map(f).map(g) <-> listA.map(f.andThen(g))
And, you are seeing exactly this.

Scala - Map2 function on Option --> flatMap vs. Map vs. For-Comprehension

Option is used for dealing with partiality in Scala, but we can also lift ordinary functions to the context of Options in order to handle errors. When implementing the function map2 I am curious on how to know when to use which functions. Consider the following implementation:
def map2[A,B,C] (ao: Option[A], bo: Option[B]) (f: (A,B) => C): Option[C] =
ao flatMap {aa =>
bo map {bb =>
f(aa, bb)
aa is of type A, and bb is of type B which is then fed to F, giving us a C. However, if we do the following:
def map2_1[A,B,C] (ao: Option[A], bo: Option[B]) (f: (A,B) => C): Option[C] =
ao flatMap {aa =>
bo flatMap {bb =>
f(aa, bb)
aa is still of type A, and bb is still of type B, yet we will have to wrap the last call in Some(f(aa, bb)) in order to get an Option[C] instead of a regular C. Why is this? What does it mean to flatten on BO here?
Last and not least, one could do the simpler:
def map2_2[A,B,C] (ao: Option[A], bo: Option[B]) (f: (A,B) => C): Option[C] = for {
as <- ao
bs <- bo
} yield(f(as,bs))
I know that for-comprehensions are syntactic sugar for ForEach'es, maps and flatmaps etc, but how do I, as a developer, know that the compiler will choose MAP with bs <- bo, and not flatMap?
I think I am on the verge of understanding the difference, yet nested flatmaps confuse me.
Taking the last question first, the developer knows what the compiler will do with for because the behaviour is defined and predictable: All <- turn into flatMap except the last one which will be either map or foreach depending on whether or not there is a yield.
The broader question seems to be about the difference between map and flatMap. The difference should be clear from the signatures e.g. for List these are the (simplified) signatures:
def map[B] (f: A => B) : List[B]
def flatMap[B](f: A => List[B]): List[B]
So map just replaces the values in a List with new values by applying f to each element of type A to generate a B.
flatMap generates a new list by concatenating the results of calling f on each element of the original List. It is equivalent to map followed by flatten (hence the name).
Intuitively, map is a one-for-one replacement whereas flatMap allows each element in the original List to generate 0 or more new elements.

Why List.fill method has two group of parameters instead of one?

What is the reason behind List.fill is being defined with two groups of parameters instead of one with n and elem parameters together
Current definition
def fill[A](n: Int)(elem: ⇒ A): CC[A]
Proposed definition
def fill[A](n: Int, elem: ⇒ A): CC[A]
Isn't it unnecessary boilerplate? Or is it designed to use the first part (List.fill(n)) as a curried function constructor?
You can write
List.fill(10){ val r = math.random; r * r }
but you cannot write
List.fill(10, r = math.random; r * r)
and
List.fill(10, {r = math.random; r * r})
looks somewhat awkward.
In this case, it's almost irrelevant, but note that the way how the arguments are grouped into argument lists can influence the type inference quite significantly, e.g.
def map[X, Y](a: F[X])(f: X => Y): F[Y]
works perfectly fine without any type annotations most of the time, whereas
def map[X, Y](a: F[X], f: X => Y): F[Y]
is quite painful to use. Take a careful look at such methods as ap, ap2 or map2 in this piece of code, for example. There is a good reason why the argument lists are the way they are, you would notice it immediately if they were defined differently.

Scala: how do I understand the curry mechanism

I understand how does a curried function work in practice.
def plainSum(a: Int)(b: Int) = a + b
val plusOne = plainSum(1) _
where plusOne is a curried function of type (Int) => Int, which can be applied to an Int:
plusOne(10)
res0: Int = 11
Independently, when reading the book (Chapter 2) Functional Programming in Scala, by Chiusano and Bjarnason, it demonstrated that the implementation of currying a function f of two arguments into a function of one argument can be written in the following way:
def curry[A, B, C](f: (A, B) => C): A => (B => C) =
a: A => b: B => f(a, b)
Reference: https://github.com/fpinscala/fpinscala/blob/master/answers/src/main/scala/fpinscala/gettingstarted/GettingStarted.scala#L157-L158
I can understand the above implementation, but have a hard time associating the signature with the plainSum and plusOne example.
The 1 in the plainSum(1) _ seems to correspond to the type parameter A, and the function value plusOne seems to correspond to the function signature B => C.
How does the Scala compiler apply the above curry signature when seeing the statement plainSum(1) _?
You are conflating partially applying a function with currying. In Scala, they some differences:
A partially applied function passes less arguments than provided in the application with the rest of the arguments, represented by the placeholder(_), is partially applied on the next call.
Currying is when a higher order function takes a function of N arguments and transforms it into a one-arg chains of functions.
The plusOne example is naturally curried out of the box by virtue of the multi-parameter list which takes a function of one argument successively and return the last argument.
Your mistake is that you are trying to use currying twice when this notation()() already gives you currying.
Meanwhile you can achieve same effect by currying the plainSum signature to the curry function like so:
def curry[A, B, C](f: (A, B) => C): A => (B => C) =
(a: A) => (b: B) => f(a, b)
def plainSum(a: Int, b: Int) = a + b
val curriedSum = curry(plainSum)
val add2 = curriedSum(2)
add2(3)
Both(partial application and currying) shouldn't be confused with another concept called partial functions.
Note: The red book, fpinscala, tried creating those abstraction as done in the Scala library without the syntactic sugar.

How to extract an element from an HList with a specific (parameterized) type

I'm chaining transformations, and I'd like to accumulate the result of each transformation so that it can potentially be used in any subsequent step, and also so that all the results are available at the end (mostly for debugging purposes). There are several steps and from time to time I need to add a new step or change the inputs for a step.
HList seems to offer a convenient way to collect the results in a flexible but still type-safe way. But I'd rather not complicate the actual steps by making them deal with the HList and the accompanying business.
Here's a simplified version of the combinator I'd like to write, which isn't working. The idea is that given an HList containing an A, and the index of A, and a function from A -> B, mapNth will extract the A, run the function, and cons the result onto the list. The resulting extended list captures the type of the new result, so several of these mapNth-ified steps can be composed to produce a list containing the result from each step:
def mapNth[L <: HList, A, B]
(l: L, index: Nat, f: A => B)
(implicit at: shapeless.ops.hlist.At[L, index.N]):
B :: L =
f(l(index)) :: l
Incidentally, I'll also need map2Nth taking two indices and f: (A, B) => C, but I believe the issues are the same.
However, mapNth does not compile, saying l(index) has type at.Out, but f's argument should be A. That's correct, of course, so what I suppose I need is a way to provide evidence that at.Out is in fact A (or, at.Out <: A).
Is there a way to express that constraint? I believe it will have to take the form of an implicit, because of course the constraint can only be checked when mapNth is applied to a particular list and function.
You're exactly right about needing evidence that at.Out is A, and you can provide that evidence by including the value of the type member in at's type:
def mapNth[L <: HList, A, B]
(l: L, index: Nat, f: A => B)
(implicit at: shapeless.ops.hlist.At[L, index.N] { type Out = A }):
B :: L =
f(l(index)) :: l
The companion objects for type classes like At in Shapeless also define an Aux type that includes the output type as a final type parameter.
def mapNth[L <: HList, A, B]
(l: L, index: Nat, f: A => B)
(implicit at: shapeless.ops.hlist.At.Aux[L, index.N, A]):
B :: L =
f(l(index)) :: l
This is pretty much equivalent but more idiomatic (and it looks a little nicer).