typelevel / mouse Goto Github PK
View Code? Open in Web Editor NEWA small companion to cats
License: MIT License
A small companion to cats
License: MIT License
see https://github.com/kailuowang/cats/blob/0dbbb365cb763d1633027364ca008e61edfe680e/docs/src/main/tut/index.md#getting-started
I added a section for cats modules with independent repos, like cats-effect cats-mtl and alleycats, can we include mouse in that list too?
Consider code test coverage reports via https://codecov.io/ ?
It's currently being used in Cats. Just wondering if you want to keep the same standards/tools?
The time has come! π
Do you think it would be useful to have mapValuesF
in MapOps
? I recently had to get from Map[A, B]
to F[Map[A, C]]
(instead of Map[A, F[C]]
) when mapping values with B => F[C]
function. After some time I've realized that it can be done with parUnorderedTraverse
, but it wasn't too obvious - my first thought was to check if there's mapValuesF
in Mouse.
When it comes to mapValuesF
definition for MapOps
, it could look like this:
import cats.syntax.parallel._
def mapValuesF[F[_], C](f: B => F[C])(implicit F: CommutativeApplicative[F], P: Parallel.Aux[F, F]): F[Map[A, C]] =
map.parUnorderedTraverse(f)
What do you think? There's is a quite popular thread on StackOverflow related to this topic so maybe it would be useful to have mapValuesF
here
If we lock binary compatibility post 1.0, I think the main implication is that we can't add methods existing traits, however we can still add new traits.
If someone want to add a new extension to Option
, he just need to create a new trait say OptionSyntax2
and let the
mouse.all
extends and mouse.option
extend this new trait.
Here is an example of this solution I am trying to suggest in a cats PR
typelevel/cats#2138 (comment)
In the case of implicit extension namespace collusion, I don't know a good solution, I imagine when user encounter this they just need to stop using the all-inclusive import and use specific imports to avoid the ones that causing conflicts.
There was recent cats 2.1.0 update, and latest mouse version is still at 2.0.0-RC. WDYT?
Firstly, thanks for all the hard work on the project!
It appears the latest version of mouse has only been published for Scala 2.12: https://mvnrepository.com/artifact/org.typelevel/mouse.
The constructor for java.net.URL
throws MalformedURLException
if the String
provided to the constructor is not a valid URL. I often find myself writing utility functions of the form String => Either[MalformedUrlException, URL]
to handle this case in a typesafe way.
This seems like something that might be appropriate to add to a syntax for String
in mouse
. Something like a parseUrl
extension method that returns Either[MalformedUrlException, URL]
. Unfortunately it doesn't seem like the java.net.URL
class exists in Scala.js.
I'm wondering if a PR would be welcome that adds a parseUrl
syntax only for the JVM version of mouse
? It may have some impact on how the package object is constructed, but I'm happy to try it out and submit a PR.
What do you think about adding toRightIn
for IO[Option[_]]
and toOptionIn
for IO[Either[_, _]]
? Having those two allows us to switch between Either
and Option
without the need to wrap toRight
/toOption
in map
(namely, it's the same case as with mapIn
or flatMapIn
).
Here's some example:
val eitherIO: IO[Either[String, Int]] = ...
val res1: IO[Option[Int]] = eitherIO.toOptionIn
// ...instead of: eitherIO.map(_.toOption)
val optionIO: IO[Option[BigInt]] = ...
val res2: IO[Either[String, BigInt]] = optionIO.toRightIn(left = "whoops")
// ...instead of: optionIO.map(_.toRight(left = "whoops"))
For those two, the implementation might be the following:
final class FOptionOps[F[_], A](private val foa: F[Option[A]]) extends AnyVal {
// (...)
def toRightIn[L](left: => L)(implicit F: Functor[F]): F[Either[L, A]] =
F.map(foa) {
case None => Left(left)
case Some(a) => Right(a)
}
}
and:
final class FEitherOps[F[_], L, R](private val felr: F[Either[L, R]]) extends AnyVal {
// (...)
def toOptionIn(implicit F: Functor[F]): F[Option[R]] =
F.map(felr)(_.toOption)
}
I'm not 100% sure about the naming, so if there's anything that more accurately matches Mouse conventions then just treat toRightIn
/toOptionIn
only as a suggestion π
Motivation
I really like flatMap
.
I also really like parMapN
on tuples.
I therefore suspect I'd really love a parFlatMapN
currently:
val helloF: F[String] = (someF, someOtherF).parMapN((a,b) => "hello")
val worldFF: F[F[String]] = (someF, someOtherF).parMapN((a,b) => "world".pure[F])
val worldF: F[String] = stringFF.flatten
what I'd love to have:
val helloF: F[String] = (someF, someOtherF).parMapN((a,b) =>"hello")
val worldF: F[String] = (someF, someOtherF).parFlatMapN((a,b) => "world".pure[F])
Is there another way to accomplish this than parFlatMapN
Yep, but I think it'd be quite nifty to have it.
The argument against is I suspect that you can just do .parMapN(..).flatten - it's not that much more code to look at - but wouldn't the same argument apply to most flatMap operations (you could just do .map(...).flatten)?
It came up in a $DAYJOB example that looked not-really-but-kind-of-sort-of like this (using cats-effects IO here for the sake of the example)
def ioEnrichOrAbsent(thing: Thing): IO[Option[Enriched]] = ???
val enriched: IO[List[Enriched]] = (ioAPICall, ioSomeOtherAPICall).parMapN {
(a,b) => {
val listOfThings: List[Thing] = getListToTraverse(a,b)
listOfThings
.parTraverse(ioEnrichOrAbsent)
.map(_.flatten)
}
}.flatten
... which, yes, in this simplified could be refactored this to be more legible in a myriad ways. But, I still think parFlatMapN would have made it more obvious than a flatten (especially because I'm already using .flatten for the List[Option[A]]` inside the parMapN, so there's a bit of mental gymnastics people have to do to realize which thing is being flattened and why)
@danicheg Could you check the draft notes at https://github.com/typelevel/mouse/releases/tag/untagged-f198bc661c03a6ac0c20 and see if the description of tuple changes is complete & accurate. Once you're happy close this issue.
I think this repo should be moved into a cats submodule.
Keep the artifact name.
This would simplify PRs to cats (recently we have seen several PRs that we say would be better as mouse PRs) and it would reduce the maintenance load since all the build updates and publishing could be done in one go.
This is stolen from typelevel/sbt-typelevel#166. Actually, kudos to @armanbilge for this finding.
https://github.com/typelevel/mouse/runs/5172959686?check_suite_focus=true#step:14:186
[warn] bad option '-doc-external-doc:/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/typelevel/cats-core_3/2.7.0/cats-core_3-2.7.0.jar#http://typelevel.org/cats/api/,/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.6/scala-library-2.13.6.jar#https://www.scala-lang.org/api/2.13.6/,/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/typelevel/cats-kernel_3/2.7.0/cats-kernel_3-2.7.0.jar#http://typelevel.org/cats/api/' was ignored
Probably that's not the Mouse problem itself and the fix should be within SBT. But let's investigate this a bit.
Dotty-compatibility mode provides a way to use mouse now in Scala 3 projects, eg
("org.typelevel" %% "mouse" % "0.25").withDottyCompat(dottyVersion)
However, Scala 3 migration is also a good time for any major refactors of the lib. Im thinking about a Scala 3 only version which would allow a clean break.
If you have any ideas/requests for a Scala 3 rewrite, please add them to this issue
It may make sense for the option
and xor
methods on BooleanOps to take by-name parameters so that they don't evaluate arguments that they may not end up using. This would make them consistent with methods like Option.getOrElse
.
Scala 3 has changed the policy about compatibility (it quite differ from Scala 2): https://twitter.com/SethTisue/status/1450260785762947076
Probably the only decision is to choose the base Scala 3 version and support only it.
So we should decide which version should be the base for mouse
β 3.0.2
or 3.1.X
.
There are a bunch of different syntaxes in the mouse
- for boxed primitive-like data types (Int
, Long
, Boolean
, etc), for collections (Map
, Set
, List
, etc), for nested things (F[Either[A, B]]
, F[Option[A]]
, etc). And as you probably already guessed, we can combine some of those in groups - mouse.collections
, mouse.nested
, etc. It'd be a bit nicer from a design perspective, as well as from navigation. But this would require us to do a new major release since that change isn't binary compatible. What do you think about it?
As have we discussed with @benhutchison in #243 mouse
documentation is needed enhancements. I propose to create a microsite for it, where we can nicely organize more detailed docs. What do you folks think about it?
According to https://github.com/orgs/typelevel/teams/maintainers/discussions/6, we need to remove some secrets in the repo settings:
Asking for help @benhutchison, I have no admin rights.
This is a minor thing but should be common for all typelevel
projects, I guess.
Hey there,
The mapKeys
is not pure because you can pass a function that will map two different input keys to the same output key. The result in such cases depends on the iteration order of the Map
and is hence not deterministic. I don't think this is appropriate for a pure FP library, so I propose to deprecate this method.
Currently PartialFunctionLift comes in on AllSyntax but it's not possible to
import mouse.partialFunction._
Additionally the syntax is for a wrapper instead of an extended method. Would it be in mouse style to change this around?
my pf: PartialFunction[Int, String] = { case 1 => "one"; case 2 => "two" }
pf.liftEither(identity)(7) // Left(7)
Given that everything else uses this style there might be something going on I'm not aware of so I wanted to raise the issue before submitting a patch.
final class BooleanOps(private val b: Boolean) extends AnyVal {
@inline def whenA[F[_], A](fa: F[A])(implicit F: Applicative[F]): F[Unit] = F.whenA(b)(fa)
@inline def unlessA[F[_], A](fa: F[A])(implicit F: Applicative[F]): F[Unit] = F.unlessA(b)(fa)
}
Motivation: Having to put the branch condition after the action it guards hurts readability. Consider:
getCondition.flatMap(
// We won't know that this is conditional until two lines later
doSomething
.productR(doSomethingElse)
.whenA(_)
)
// versus
getCondition.flatMap {
// The condition is front and center
_.whenA(
doSomething
.productR(doSomethingElse)
)
}
It gets even more annoying if you have something else to do after the conditional branch - the two blocks of logic are not visually separated:
doSomething
.productR(doSomethingElse)
.whenA(condition)
.productR(alwaysDoThis)
// versus
condition.whenA(
doSomething
.productR(doSomethingElse)
).productR(alwaysDoThis)
Both whenA
and unlessA
are present in Scalaz as whenM
and unlessM
.
I often talk to colleagues who use cats-core, cats-effect, etc... but know nothing about mouse.
This is sad because I love mouse and all its features ( which prevents the usage of EitherT
and OptionT
for instance) , but I see the reasons why this can't be merged within cats-core.
However, I wonder if there is a way to improve the visibility of this project.
With the arrival of Scala 3 we can take the opportunity to rename/fork this library to a better and clearer name which includes cats-
as prefix to let it be more part of an ecosystem. I see cats-core as the base, mouse as an advanced syntax of cats and cats-effect as the effects system.
At the moment, mouse
seems an independent library and not a cats ecosystem add-on to me.
I don't want to blame the current name, I just wonder if a rebranding would improve the visibility of this library which, in my humble opinion, should be part of every cats-based project. Too many times I have seen boilerplate and operators that are already part of mouse rewritten within Utils
classes.
Going deeper, the problem I see with the mouse
name is that it doesn't really describe the purpose of the library, which aim to just enrich cats with a nice and useful syntax without adding new type classes or features.
Some ideas:
cats-pragmatics
(polished, but I'm not sure if it is correct)cats-mouse
( more conservative )cats-suit
/ cats-dress
( fancy and funny but I don't know how explanatory it is )cats-enrichments
/ cats-rich-syntax
( more serious but clear )Something like this, for instance:
"org.typelevel" %% "cats-core" % "x.y.z",
"org.typelevel" %% "cats-rich-syntax" % "x.y.z",
"org.typelevel" %% "cats-effect" % "x.y.z",
Once done with the name, I'd also rethink the packages because it would be nice to try to keep the same pattern as cats-core
in order to simplify the life of the users.
Since mouse adds syntax to cats core, maybe it is correct to put it under cats.syntax
package, where mouse is a specialization of the basic cats syntax. This creates a kind of boundary.
Moreover, this is also useful to better organize imports with Scalafmt
rewrite.rules = [ SortImports ]
Example
// current
import cats.syntax.all.* // imports all cats-core syntax
import mouse.all.* // import all mouse syntax
// new
import cats.syntax.all.* // imports all cats-core syntax
import cats.syntax.rich.all.* // import all cats rich syntax
We could also consider to no having a global import but split them by type as suggested in #362 which I like TBH
import cats.syntax.collections.* // import all cats boxed syntax
import cats.syntax.nested.* // import all cats nested syntax
//or
import cats.syntax.rich.collections.* // import all cats boxed syntax
import cats.syntax.rich.nested.* // import all cats nested syntax
To rename or not, what name and how to do it is up to you, but I just wanted to sow a seed for a discussion.
Maybe I am the only one who sees this problem.
Let me know what you think :)
Hi.
It's unclear what the boundaries of this library are for submission. Most of the functionality integrates with cats, but there are a few utility functions that are stand-alone working with basic types (see Int in Readme.md). I'm able to offer the latter, but would like to hear whether my submission would be accepted before I do additional work to polish it / respond to scrutiny outside the scope of my personal project. These would end up in Int/Long and String.
object EndianUtils {
def toBEInteger(a: Array[Byte], offset: Int, numBytes: Int, signExtend: Boolean = false): Int = {
val fillBytes = 4 - numBytes
val i = if (fillBytes > 0) {
val fill = if (signExtend && (a(0 + offset) & 0x80) == 0x80) 0xFF else 0
(0 until fillBytes).foldLeft(0)((acc, _) => acc << 8 | fill)
} else 0
(0 until numBytes).foldLeft(i)((acc, index) => acc << 8 | (a(index + offset) & 0xFF))
}
def toLEInteger(a: Array[Byte], offset: Int, numBytes: Int, signExtend: Boolean = false): Int = {
val fillBytes = 4 - numBytes
val i = if (fillBytes > 0) {
val fill = if (signExtend && (a(numBytes - 1 + offset) & 0x80) == 0x80) 0xFF else 0
(0 until fillBytes).foldLeft(0)((acc, _) => acc << 8 | fill)
} else 0
(numBytes-1 to 0 by -1).foldLeft(i)((acc, index) => acc << 8 | (a(index + offset) & 0xFF))
}
def toBELong(a: Array[Byte], offset: Int, numBytes: Int, signExtend: Boolean = false): Long = {
val fillBytes = 8 - numBytes
val i = if (fillBytes > 0) {
val fill = if (signExtend && (a(0 + offset) & 0x80) == 0x80) 0xFFL else 0L
(0 until fillBytes).foldLeft(0L)((acc, _) => acc << 8 | fill)
} else 0L
(0 until numBytes).foldLeft(i)((acc, index) => acc << 8 | (a(index + offset) & 0xFF))
}
def toLELong(a: Array[Byte], offset: Int, numBytes: Int, signExtend: Boolean = false): Long = {
val fillBytes = 8 - numBytes
val i = if (fillBytes > 0) {
val fill = if (signExtend && (a(numBytes - 1 + offset) & 0x80) == 0x80) 0xFFL else 0L
(0 until fillBytes).foldLeft(0L)((acc, _) => acc << 8 | fill)
} else 0L
(numBytes-1 to 0 by -1).foldLeft(i)((acc, index) => acc << 8 | (a(index + offset) & 0xFF))
}
// These only work with unsigned ints
def intToBEArray(v: Int, a: Array[Byte], offset: Int, numBytes: Int): Unit =
for (i <- 0 until numBytes)
a(offset + i) = (v >>> ((numBytes - i - 1) << 3)).toByte
def intToLEArray(v: Int, a: Array[Byte], offset: Int, numBytes: Int): Unit =
for (i <- 0 until numBytes)
a(offset + i) = (v >>> (i << 3)).toByte
def longToBEArray(v: Long, a: Array[Byte], offset: Int, numBytes: Int): Unit =
for (i <- 0 until numBytes)
a(offset + i) = (v >>> ((numBytes - i - 1) << 3)).toByte
def longToLEArray(v: Long, a: Array[Byte], offset: Int, numBytes: Int): Unit =
for (i <- 0 until numBytes)
a(offset + i) = (v >>> (i << 3)).toByte
}
import java.nio.charset.StandardCharsets
import scala.annotation.tailrec
object StringUtils {
val charset = StandardCharsets.ISO_8859_1
def zeroTerminatedStringFromArray(a: Array[Byte], offset: Int, maxLength: Int): String = {
val maxLengthAndOffset = maxLength + offset
@tailrec
def findZeroTerminator(i: Int): Int =
if (i == maxLengthAndOffset) i
else if (a(i) == 0) i
else findZeroTerminator(i+1)
val end = findZeroTerminator(offset)
new String(a, offset, end - offset, charset)
}
def stringToArray(str: String, a: Array[Byte], offset: Int, maxLength: Int): Unit = {
val bytes = str.getBytes(charset)
val strLengthToCopy = Math.min(maxLength, bytes.size)
System.arraycopy(bytes, 0, a, offset, strLengthToCopy)
if (strLengthToCopy < maxLength)
a(offset + strLengthToCopy) = 0
}
}
Is there a reason the Boolean functions whenA
and unlessA
are not by-name (lazy) functions like option
, xor
, fold
etc?
Now when using false.whenA(f)
function f
is evaluated even when the boolean is false. Similar for unlessA
.
Is this a bug or a feature?
Functions like Option.when
and Option.unless
are also by-name, so I would have expected these Boolean function to also be by-name.
What do you think about adding mapInF
to both FOptionOps
and FEitherOps
?
// FOptionOps:
def mapInF[B](f: A => F[B])(implicit F: Monad[F]): F[Option[B]] = ???
// FEitherOps:
def mapInF[A](f: R => F[A])(implicit F: Monad[F]): F[Either[L, A]] = ???
I think mouse should have |>
and ignore
, a la F#.
mouse
being a separate module of cats
, is it possible to move to the typelevel org? Having it under a different org just seems not as "official" as other separate modules of cats
like cats-mtl
and cats-effects
( I am raising the same issue in alleycats
), and thus hurts its popularity, which is a shame.
Can we please get a version of mouse that is build against cats 1.1.0? :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.