Open gcanti opened 7 years ago
I implemented Option and Either based on what you did in fp-ts
and will gladly create a PR (both Option and Either are covered with tests). However I'm not sure what direction do you want to take with this. Will this implementation replace the one with inj/prj
? Also as flowtype
doesn't have module augmentation some of the things you do in fp-ts
are note transferrable to flow. Do you have ideas on how it can possibly be done.
Another thing I was thinking about is making Option and Either a bit more accessible to the general js programmer. The amount of libraries that have their own Option/Maybe implementation is growing in a way similar to Promises libraries at some point in the past. Unfortunately the majority of them throw category theory in your face first thing in the Readme file. I don't think it actually encourages people to just grab and use it as it presents itself as a more complex matter than it is (for general use cases).
Take Scala's Option — without Cats/Scalaz it's a very simple datatype that is used by Scala's prelude functions. I guess for the most part It's more of a documentation issue which I'll be glad to help with if you feel like it can be useful.
Another thing — a bit of a blasphemy for pure fantasy land folks — can we think of a method similar to Promise.then
which can combine map and flatMap/chain for convenience? We still keep map and chain but by making the API similar to Promises we might push Option a bit closer to be standardised across the libraries and possibly in the language itself. I'm sure people would rather have an Either being returned by JSON.parse rather then wrapping it into a try/catch.
Will this implementation replace the one with inj/prj?
If we go the fantasy-land route then yes inj/prj
are replaced by new / this.value
(when possible, for example the module Arr
, if we don't want to wrap the native arrays, should keep the inj/prj
pair I guess )
Also as flowtype doesn't have module augmentation some of the things you do in fp-ts are note transferrable to flow
We can define overloadings though it's not the same (augmentations can be distributed while overloadings are centralised)
Example with lift
and Option
// file Functor.js
import type { Option, URI as OptionURI } from './Option'
export interface Functor<F> {
map<A, B>(f: (a: A) => B, fa: HKT<F, A>): HKT<F, B>
}
// more overloadings here...
declare function lift<A, B>(functor: Functor<OptionURI>, f: (a: A) => B): (fa: Option<A>) => Option<B>
export function lift<F, A, B>(functor: Functor<F>, f: (a: A) => B): (fa: HKT<F, A>) => HKT<F, B> {
return fa => functor.map(f, fa)
}
can we think of a method similar to Promise.then which can combine map and flatMap/chain for convenience?
Not sure what you gain from merging map
and chain
though
Will this implementation replace the one with inj/prj?
Actually we could also implement a fromNullable / toNullable
pair
export function fromNullable<A>(a: ?A): Option<A> {
return a == null ? none : some(a)
}
export function toNullable<A>(fa: Option<A>): ?A {
return fa instanceof Some ? fa.value : null
}
and then alias them to inj / prj
to keep the old (deprecated) API unchanged
[DISCLAIMER] I switched to TypeScript and I think I won't look back anytime soon, so I'll dedicate much less time to flow-static-land
@gcanti do you have any write ups anywhere describing why you made the switch? I thought that the type variance support of flow would play nicer with some advanced FP concepts, but perhaps I am misguided. :)
@ctrlplusb No, I only have a list of pros and cons (presented in an internal meeting at buildo) half in english and half in italian.
The gist is that TypeScript is a great tool from a pragmatic point of view. Also indexed types are awesome.
the type variance support of flow would play nicer with some advanced FP concepts
Indeed, that and its advanced type inference are what I miss the most, especially in the context of functional programming.
Is this something you're still considering?
While I know you don't prefer point-free programming, I'd love to see this! @drboolean
export const map = <A, B>(f: A => B): (HKTOption<A> => Option<B>) => fa => {
return ((fa: any): Option<A>).map(f)
}
const foo = compose(
map(log),
map(double),
some,
)
foo(10) // Some(20)
Seems like this should error?
console.log(maybeDouble(some(''))) // No Error
This fixes the above error...
class None {
__hkt: URI
__hkta: any
// ...
}
to...
class None<A> {
__hkt: URI
__hkta: A
// ...
}
The any
breaks the error checking here...
With the motivation of making things more clear & approachable, I'm curious what you all think of the following changes:
Nothing.of()
) instead of const none = new None()
& function some<A>(a: A): Option<A> { return new Some(a) }
Kind
instead of HKT
__type
instance property to denote Kind
URI
implements
to show relationship to Kind
.// @flow
interface Kind<F, A> {
__type: [ F, A ]
}
interface Functor<F> {
map<A, B>(f: A => B, fa: Kind<F, A>): Kind<F, B>
}
type Maybe<A> = Nothing<A> | Just<A>
class Nothing<A> implements Kind<'Maybe', A> {
__type: [ 'Maybe', A ]
static of(): Maybe<A> {
return new Nothing()
}
map<B>(f: A => B): Maybe<B> {
return (Nothing.of(): any)
}
inspect(): string {
return this.toString()
}
toString(): string {
return 'Nothing'
}
}
class Just<A> implements Kind<'Maybe', A> {
__type: [ 'Maybe', A ]
value: A
static of(value: A): Maybe<A> {
return new Just(value)
}
constructor(value: A) {
this.value = value
}
map<B>(f: A => B): Maybe<B> {
return Just.of(f(this.value))
}
inspect(): string {
return this.toString()
}
toString(): string {
return `Just(${JSON.stringify(this.value)})`
}
}
export function map<A, B>(f: A => B, fa: Kind<'Maybe', A>): Maybe<B> {
return ((fa: any): Maybe<A>).map(f)
}
const double = (n: number): number => n * 2
const length = (s: string): number => s.length
function lift<F, A, B>(functor: Functor<F>, f: A => B): Kind<F, A> => Kind<F, B> {
return fa => functor.map(f, fa)
}
const maybeDouble = lift({ map }, double)
console.log(maybeDouble(Just.of(10))) // Just(20)
console.log(Just.of(1).map(double)) // Just(2)
console.log(Nothing.of().map(double)) // Nothing
console.log(Just.of(1).map(length)) // error: number. This type is incompatible with the expected param type of string
Note that flow now has opaque types
, which can help to declare type relations
https://medium.com/flow-type/hiding-implementation-details-with-flows-new-opaque-type-aliases-feature-40e188c2a3f9
$Values and $ElementType can help too
Also, if you use comment syntax, you can avoid unnecessarily real code - no if (false) {
no more
Proof of concept (borrowed from fp-ts)