-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Code review: is this the right way to implement Option? #261
Comments
I spotted a couple of problems:
Rather than creating yet another Maybe/Option implementation I would love to see you add TypeScript support to a mature implementation such as those of Folktale and Sanctuary. |
100% this |
You could certainly implement something like I think that implementing a type like |
https://github.com/sanctuary-js/sanctuary-maybe/tree/svozza/everything would be a good place to look. |
Those functor laws :d |
Thanks for the feedback everyone - it's very helpful.
Looking here the methods seem to be directly on the promise prototype. But in @gabejohnson's example methods are namespaced. Which convention should be used where?
At least in Scala, this behavior is an unfortunate consequence of running on the JVM. I changed that behavior is tsoption, since |
@bcherny there's nothing about the FL spec preventing you from providing non-namespaced methods on the prototype. The example code you linked to is following the pre-1.0 spec which didn't have prefixing. |
It's forbidden by the spec:
This doesn't prevent authors from supporting |
@gabejohnson Branch is updated - how does that look? @davidchambers A couple of points:
On another note, a few thoughts as a first time user:
Edit: Added more thoughts. |
Equivalent, in this case, doesn't actually mean either of these. See #259.
Correct.
The functions in the chain should have types such as Think about getting rid of all the
I agree. I think we should use the
It's not necessary to depend on For users, I agree that
Unfortunately such proofs are not possible even with Haskell's type system (which is much more powerful than that of TypeScript). Instead we must rely on property-based testing and good old-fashioned reasoning.
I completed the remaining tasks on fantasyland/fantasy-laws#1 this weekend. That project should be available via npm within the next couple of weeks. :) |
You may want to look at Jabz which is exactly that. But, as @davidchambers rightly sais TypeScript cannot verify the laws.
Or even better: Remove the prefixes altogether 😄 They give nothing but problems, inconvenience and added complexity. |
Really? Based on ramda/ramda#2223 I must disagree. |
I don't agree. It give interoperability of APIs preventing duck typing conflicts. |
Thanks for the detailed answer @davidchambers. I'm still not sure I totally get it.
flatMap<U>(f: (value: T) => Some<U>): Some<U>
flatMap<U>(f: (value: T) => None<U>): None<T> But you're saying map<U>(f: (value: T) => null): None<U>
map<U>(f: (value: T) => U): Some<U> Let me know if I got that right. What I don't understand is how Are you saying that a function passed to map<U>(f: (value: T) => U): Some<U> If so, that seems to be sacrificing usability for perfection: it puts the burden on the user to lift every nullable function to an map<U>(f: (value: T) => null): never
map<U>(f: (value: T) => U): Some<U> But that code suffers from nonlocality: a nullable function passed to I'm curious what you think, and if I understood you correctly so far. |
tl;dr; The long and short is, we've defined the spec to be a certain way, We've discussed this pretty in depth in the past: #85 I mention this not to dissuade you from asking questions, but in order to show you some of the past discussions we've had. There's a good chance the reasoning is down in that issue. There's also a really good chance that any other algebra you try to adhere to are broken if Here are some highlights:
There are more, hopefully with better explanations. Again, please feel free to ask questions, but there's also past discussions that might answer your questions. |
Thanks for explaining the incentive TypeScript is providing to work with As Hardy stated above you're free to treat As for interoperability, it cuts both ways. If your Option type is not lawful you cannot leverage useful functions such as these: $ cd github.com/sanctuary-js/sanctuary
$ grep -E '//# [^#]* :: .*(Alt|Alternative|Applicative|Apply|Bifunctor|Chain|ChainRec|Comonad|Extend|Functor|Monad|Plus|Profunctor|Traversable)' index.js
//# map :: Functor f => (a -> b) -> f a -> f b
//# bimap :: Bifunctor f => (a -> b) -> (c -> d) -> f a c -> f b d
//# promap :: Profunctor p => (a -> b) -> (c -> d) -> p b c -> p a d
//# alt :: Alt f => f a -> f a -> f a
//# zero :: Plus f => TypeRep f -> f a
//# traverse :: (Applicative f, Traversable t) => TypeRep f -> (a -> f b) -> t a -> f (t b)
//# sequence :: (Applicative f, Traversable t) => TypeRep f -> t (f a) -> f (t a)
//# ap :: Apply f => f (a -> b) -> f a -> f b
//# lift2 :: Apply f => (a -> b -> c) -> f a -> f b -> f c
//# lift3 :: Apply f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d
//# apFirst :: Apply f => f a -> f b -> f a
//# apSecond :: Apply f => f a -> f b -> f b
//# of :: Applicative f => TypeRep f -> a -> f a
//# chain :: Chain m => (a -> m b) -> m a -> m b
//# join :: Chain m => m (m a) -> m a
//# chainRec :: ChainRec m => TypeRep m -> (a -> m (Either a b)) -> a -> m b
//# extend :: Extend w => (w a -> b) -> w a -> w b
//# extract :: Comonad w => w a -> a
//# filter :: (Applicative f, Foldable f, Monoid (f a)) => (a -> Boolean) -> f a -> f a
//# filterM :: (Alternative m, Monad m) => (a -> Boolean) -> m a -> m a
//# takeWhile :: (Foldable f, Alternative f) => (a -> Boolean) -> f a -> f a
//# dropWhile :: (Foldable f, Alternative f) => (a -> Boolean) -> f a -> f a
//# append :: (Applicative f, Semigroup (f a)) => a -> f a -> f a
//# prepend :: (Applicative f, Semigroup (f a)) => a -> f a -> f a
//# pluck :: (Accessible a, Functor f) => String -> f a -> f b
//# reverse :: (Applicative f, Foldable f, Monoid (f a)) => f a -> f a
//# sort :: (Ord a, Applicative m, Foldable m, Monoid (m a)) => m a -> m a
//# sortBy :: (Ord b, Applicative m, Foldable m, Monoid (m a)) => (a -> b) -> m a -> m a So, you have a choice:
|
@bcherny It is a popular misconception that the
|
Branch is updated - how does that look to you guys? @ivenmarquardt That seems pretty controversial. |
Closing. Thanks for the feedback everyone, and feel free to file an issue in https://github.com/bcherny/tsoption if something still isn't right. |
@bcherny The disconnect is that TypeScript has ad-hoc untagged unions, and it must interoperate with JS code that will just be producing I think if you instead claim static-land compatibility, you could implement instances of all the relevant typeclasses for a non-reified "virtual" type like As an example: const Option = {
// ...
map: (f, a) => a === undefined ? undefined : f(a)
} And then somewhere else... import { map as optMap } from "my/option"
optMap(x => x * 2, undefined); // undefined
optMap(x => x * 2, 10); // 20 |
Hey guys, first time Fantasyland implementer here.
I recently implemented an Option type for TypeScript, and took a shot at adding Fantasyland support to it. As a Haskell and category theory newbie, I'm not totally confident in my implementation. It would be really nice if someone could take a look at tsoption#fantasyland and give some feedback (in this issue tracker, or via issue or PR in its tracker).
If this is the wrong place for this, feel free to close this issue and I'll reach out to FL contributors directly.
Thanks!
The text was updated successfully, but these errors were encountered: