Missing operators in scalaz-seven

128 views
Skip to first unread message

etorreborre

unread,
Dec 11, 2011, 5:20:27 AM12/11/11
to sca...@googlegroups.com
Hi,

I've started to migrate some of my code to scalaz-seven, just to see what would be the changes. 

I think that I'm starting to get it, however some operators seems to be missing like <**> and/or |@|. Is it on purpose, or are they due to be implemented in the future?

Thanks,

Eric.

Jason Zaugg

unread,
Dec 11, 2011, 1:56:57 PM12/11/11
to sca...@googlegroups.com

Hi Eric,

I just expanded the syntax for Apply: https://github.com/scalaz/scalaz/compare/34eccc...e68531

I'm still looking for a better way to implement ApplicativeBuilder (|@|), but it's no problem to stay backward compatible and support the old style.

Let me know if you're still missing something.

-jason
 

Lars Hupel

unread,
Dec 11, 2011, 2:45:39 PM12/11/11
to sca...@googlegroups.com
> I'm still looking for a better way to implement ApplicativeBuilder (|@|),
> but it's no problem to stay backward compatible and support the old style.

We are trying to fix that using HLists [1]. Unfortunately we didn't have
time yet to port our code to scalaz-seven, but I hope we will have
something by the end of this year.

[1]
<https://github.com/larsrh/scalaz/blob/typelevel/core/src/main/scala/scalaz/hcoll/GenericList.scala#L20>

etorreborre

unread,
Dec 11, 2011, 5:28:56 PM12/11/11
to sca...@googlegroups.com
Great, thanks Jason!

I'm still doing my migration and:
  • the type-checker errors seem to be a bit more complex now (with Unapply)
  • I need to understand how my own typeclass instances should fit with the new structure
Otherwise `|>` and `>>=|` seem to be missing also but actually I don't mind about `>>=|` since `>>` is equivalent, shorter and more idiomatic (from Haskell, right?).

I'll come back to you if I have anything else.

Eric.

etorreborre

unread,
Dec 13, 2011, 2:09:16 AM12/13/11
to sca...@googlegroups.com
Jason,

I'm still going on with the migration of my app. to scalaz-seven and I found the following:

 - |> syntax is missing as I wrote before and I kinda like it in some cases
 - ! syntax for State is missing
 - I didn't see any Traverse instance for Seq
 - there is a change in the State / StateT definition:

   def apply(initial: S): F[(A, S)]

I was used to S => (S, A). Maybe you wanted to make it closer to Haskell? I actually don't know why they choose this order in Haskell but I like the fact that State[S, A] and apply: S => (S, A) have their types in the same order. What is the reason for not doing that?

Eric.

PS: otherwise I'm now down to 6 compilation errors,...

etorreborre

unread,
Dec 13, 2011, 2:40:30 AM12/13/11
to sca...@googlegroups.com
No compilation errors anymore, yay!

Now there's something I want to ask. I have some code to help with "trampolining" simple states into StateT[Trampoline, S, A]

  // to "trampoline" a State
  implicit def toTrampoline[S, A](st: State[S, A]) = new ToTrampoline(st)
  class ToTrampoline[S, A](st: State[S, A]) {
    def trampolined: StateT[Trampoline, S, A] = StateT((s: S) => Trampoline.trampolineMonad.point(st.apply(s)))
  }

  // to "untrampoline" a StateT[Trampoline, S, A]
  implicit def fromTrampoline[S, A](st: StateT[Trampoline, S, A]) = new FromTrampoline(st)
  class FromTrampoline[S, A](st: StateT[Trampoline, S, A]) {
    def untrampolined: State[S, A] = State((s: S) => st(s).run)
  }

  // to "lift" a function returning a State
  implicit def liftToTrampoline[T, S, A](f: T => State[S, A]) = new LiftToTrampoline(f)
  class LiftToTrampoline[T, S, A](f: T => State[S, A]) {
    def trampolined = (t: T) => toTrampoline(f(t)).trampolined
  }


And btw, this definition only I added in my project (replacing other typeclasses instances):

  implicit def StateTMApplicative[M1[_]: Monad, S, M2[_] : Applicative] =
    StateT.stateTMonadState[S, M1].compose(implicitly[Applicative[M2]])

is pure genius!

Eric.

Jason Zaugg

unread,
Dec 13, 2011, 2:46:17 AM12/13/11
to sca...@googlegroups.com
On Tue, Dec 13, 2011 at 5:09 PM, etorreborre <etorr...@gmail.com> wrote:
Jason,

I'm still going on with the migration of my app. to scalaz-seven and I found the following:

 - |> syntax is missing as I wrote before and I kinda like it in some cases

I haven't found the right home for all the stuff from Identity, that's one of them.
 
 - ! syntax for State is missing

`!` is so overloaded I would prefer to drop that method in favour of eval.
 
 - I didn't see any Traverse instance for Seq

I would recommend to convert to List or Stream first. We had problems in Scalaz 6 trying to generically define type class instances for abstract interface types in the Scala collections. I might add them on an opt-in basis (you have to import scalaz.std.seq._, rather than getting it via import scalaz.Scalaz._), but I'm not sure if it's worth it.
 
 - there is a change in the State / StateT definition:

   def apply(initial: S): F[(A, S)]
 
I was used to S => (S, A). Maybe you wanted to make it closer to Haskell? I actually don't know why they choose this order in Haskell but I like the fact that State[S, A] and apply: S => (S, A) have their types in the same order. What is the reason for not doing that?

(I guess you've figured out this part, but for the benefit of others I'll clarify) Firstly, the 'F' type constructor in the StateT signatures melts away:

  type Id[X] = X
  type State[S, A] = StateT[Id, S, A]

As to the ordering of (A, S) / (S, A), I'm not sure when that got switched. I don't have an opinion either way, but if there wasn't a good reason to switch we ought to stick to the Scalaz 6 order.

-jason

Jason Zaugg

unread,
Dec 13, 2011, 2:52:44 AM12/13/11
to sca...@googlegroups.com
Composition is fun! But what was your question? :)

Here's an unsolicited answer to a different question: one new convenience is that `implicitly[TC[A]]` can now be replaced with `TC[A]`.

BTW, did you stick to `import Scalaz._`, or did you try to use the more focussed imports for the type class instances and type class syntax you needed? For that matter, did you opt for 'standalone' usage of the type classes, or use the type class syntax?

-jason

etorreborre

unread,
Dec 13, 2011, 7:10:14 AM12/13/11
to sca...@googlegroups.com
> `!` is so overloaded I would prefer to drop that method in favour of eval.
 
I agree.

> I would recommend to convert to List or Stream first. 

I did that and that works fine. 
 
> As to the ordering of (A, S) / (S, A), I'm not sure when that got switched. I don't have an opinion either way, but if there wasn't a good reason to switch we ought to stick to the Scalaz 6 order.
   
Yes, I think it's better (easier to memorize).

I've also seen that you've added some specialized traverse functions, like "traverseS". I think that there's an issue with that because this creates a StackOverflow error on "big" streams. Could we instead provide a "trampolined version" for Stream by default?

   /** btw, traverseU works like magic! */
   def traverseState[S, B](f: A => State[S, B]) = seq.toStream.traverseU(f.trampolined).untrampolined

Now, here's quizz. I've defined the following function:

    def traverseStateIO[S, B](f: A => State[S, IO[B]])(init: S): Seq[B] =  {
      implicit val applicative = StateTMApplicative[Trampoline, S, IO]
      (seq.toStream.traverse[({type l[a]=StateT[Trampoline, S, IO[a]]})#l, B](f.trampolined).untrampolined eval init).unsafePerformIO
    }

I've tried to generalize this to [M[_] : Applicative] instead of IO (then returning M[Seq[B]]). But I get compilation errors involving Id (how to do get a value out of Id? with Id.id.copoint?) and Unapply. Notably the compiler complains that M is invariant in Unapply. Do you think that this generalization is possible?


    /** it would be something like that*/
    def traverseStateM[M[_] : Applicative, S, B](f: A => State[S, M[B]])(init: S): M[Seq[B]] =  {
      implicit val applicative = StateTMApplicative[Trampoline, S, M]
      Id.id.copoint(seq.toStream.traverse[({type l[a]=StateT[Trampoline, S, M[a]]})#l, B](f.trampolined).untrampolined eval init)
    }

Finally, I'm also wondering if we could define a trait UnapplyComposed like:

trait UnapplyComposed[TC[_[_]], MA] {

  type M1[_]
  type M2[_]
  type A

  /** The instance of the type class */
  def TC: TC[M1[M2[A]]

  /** Evidence that MA =:= M1[M2[A]] */
  def apply(ma: MA): M1[M2[A]]
}

and then in TraverseV:

  final def traverseComposed[GB](f: A => GB)(implicit G: Unapply[Applicative, GB]): G.M1[F[G.M2[G.A]]] = {
    G.TC.traverse(self)(a => G(f(a)))
  }

Oh yes, I forgot your question about the imports. I tried to stay with "import Scalaz._" and then restrict that if I had conflicts or add some missing imports (for syntax most notably)

For example I wanted ?? on Boolean but ToAllV is missing an extension of ToBooleanV.

There is also a place where I'm using Monoids to fold a list of key K/values V, so that I get a list of key/(added values for the same key). I had to import:

import std._
import iterable._
import map._

And also I found useful to define:

  def semigroupIsOptionMonoid[T : Semigroup] = new Monoid[Option[T]] {
    val zero: Option[T] = None
    def append(t1: Option[T], t2 : =>Option[T]): Option[T] = (t1 <**> t2)(Semigroup[T].append(_, _))
  }

That should totally make it to core (unless I missed it?)

Eric.

PS: sorry, it's a bit of an unordered list of thoughts and questions here :-)




Message has been deleted

etorreborre

unread,
Dec 13, 2011, 7:27:03 AM12/13/11
to sca...@googlegroups.com
Also, maybe it's just me, but I liked this syntax:

implicit def monads[M[_]: Monad, A](m: M[A]): Monads[M, A] = new Monads(m)
class Monads[M[_]: Bind : Pointed, A](m: M[A]) {

  def >>=?(condition: Boolean, m2: =>M[A]): M[A] = {
    m >>= { (result: A) =>
      if (condition)  m2
      else            Pointed[M].point(result)
    }
  }

}

Then I can write:

  // does the first action but not the second
  println(3).point[IO] >>=? (2 == 1, println(4).point[IO])

  // does the first action, then the second
  println(3).point[IO] >>=? (2 == 1+1, println(4).point[IO])

It's like a "conditional flatMap". Is there name for that in Haskell?

Eric.

Runar Bjarnason

unread,
Dec 13, 2011, 10:15:59 AM12/13/11
to sca...@googlegroups.com
> It's like a "conditional flatMap". Is there name for that in Haskell?

I think filterM will do what you want.

Runar Bjarnason

unread,
Dec 13, 2011, 11:11:18 AM12/13/11
to sca...@googlegroups.com
>> As to the ordering of (A, S) / (S, A), I'm not sure when that got
>> switched. I don't have an opinion either way, but if there wasn't a good
>> reason to switch we ought to stick to the Scalaz 6 order.
>
> Yes, I think it's better (easier to memorize).

I agree that we should not flip the order unnecessarily, and it's
unfortunate that the order was "wrong" in 6. The are some (good?)
reasons why it should be (A, S):

1. The Kleisli arrow for State is
A => State[S, A]
A => S => (A, S) {expanded}
(A, S) => (A, S) {uncurried}
This way, Kleisli composition mirrors ordinary function composition in Endo.

2. State[S, _] is a composition of two functors: reader and writer.
type Reader[A] = S => A
type Writer[A] = (A, S)

Where Writer is left-adjunct to Reader. That way, the composition
Reader*Writer forms a monad: S => (A, S)
and the composition Writer*Reader forms a comonad: (S => A, S), so
that the implementations of unit and counit (for the monad and
comonad, respectively) are trivial:

def leftAdjunct[A, B](f: ((A, S)) => B): A => S => B =
Function.untupled(f).curried
def rightAdjunct[A, B](f: A => S => B): ((A, S)) => B =
Function.uncurried(f).tupled
def unit[A]: A => S => (A, S) = leftAdjunct(x => x)
def counit[A]: ((S => A, S)) => A = rightAdjunct(x => x)

Jason Zaugg

unread,
Dec 14, 2011, 4:01:21 AM12/14/11
to sca...@googlegroups.com
On Tue, Dec 13, 2011 at 10:10 PM, etorreborre <etorr...@gmail.com> wrote:
I've also seen that you've added some specialized traverse functions, like "traverseS". I think that there's an issue with that because this creates a StackOverflow error on "big" streams. Could we instead provide a "trampolined version" for Stream by default?

   /** btw, traverseU works like magic! */
   def traverseState[S, B](f: A => State[S, B]) = seq.toStream.traverseU(f.trampolined).untrampolined

Now, here's quizz. I've defined the following function:

    def traverseStateIO[S, B](f: A => State[S, IO[B]])(init: S): Seq[B] =  {
      implicit val applicative = StateTMApplicative[Trampoline, S, IO]
      (seq.toStream.traverse[({type l[a]=StateT[Trampoline, S, IO[a]]})#l, B](f.trampolined).untrampolined eval init).unsafePerformIO
    }

I've tried to generalize this to [M[_] : Applicative] instead of IO (then returning M[Seq[B]]). But I get compilation errors involving Id (how to do get a value out of Id? with Id.id.copoint?) and Unapply. Notably the compiler complains that M is invariant in Unapply. Do you think that this generalization is possible?


    /** it would be something like that*/
    def traverseStateM[M[_] : Applicative, S, B](f: A => State[S, M[B]])(init: S): M[Seq[B]] =  {
      implicit val applicative = StateTMApplicative[Trampoline, S, M]
      Id.id.copoint(seq.toStream.traverse[({type l[a]=StateT[Trampoline, S, M[a]]})#l, B](f.trampolined).untrampolined eval init)
    }

You don't need to get a value out of Id[X], because that just *is* X. copoint is just the identity function. 


I generalized your trampolined/untrampolined to StateT#{lift, unlift}. (Anyone other suggestions for names for these? Should we add these to MonadTrans, and offer them for the other transformers?)

Also added Copointed[Trampoline] (Does this make sense / any other instance that it could support?)
 
Finally, I'm also wondering if we could define a trait UnapplyComposed like:

trait UnapplyComposed[TC[_[_]], MA] {

  type M1[_]
  type M2[_]
  type A

  /** The instance of the type class */
  def TC: TC[M1[M2[A]]

  /** Evidence that MA =:= M1[M2[A]] */
  def apply(ma: MA): M1[M2[A]]
}

and then in TraverseV:

  final def traverseComposed[GB](f: A => GB)(implicit G: Unapply[Applicative, GB]): G.M1[F[G.M2[G.A]]] = {
    G.TC.traverse(self)(a => G(f(a)))
  }

Maybe. But the number of implicits needed to back this will be n^2, as compared to Unapply, for the reasons outlined in the comments in UnapplyProduct. Some small changes to scalac might turn things back in our favour. 

Oh yes, I forgot your question about the imports. I tried to stay with "import Scalaz._" and then restrict that if I had conflicts or add some missing imports (for syntax most notably)

For example I wanted ?? on Boolean but ToAllV is missing an extension of ToBooleanV.
 
There is also a place where I'm using Monoids to fold a list of key K/values V, so that I get a list of key/(added values for the same key). I had to import:

import std._
import iterable._
import map._

I've added ToBooleanV and MapInstances to the right places. IterableInstances is still and opt-in import, to avoid ambiguity or wrong instance selection.

And also I found useful to define:

  def semigroupIsOptionMonoid[T : Semigroup] = new Monoid[Option[T]] {
    val zero: Option[T] = None
    def append(t1: Option[T], t2 : =>Option[T]): Option[T] = (t1 <**> t2)(Semigroup[T].append(_, _))
  }

That should totally make it to core (unless I missed it?)

OptionInstances#optionMonoid is exactly that. 

Thanks again for the feedback.

-jason
Reply all
Reply to author
Forward
0 new messages