-
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
Move all functions to namespaced Type Representative #199
Comments
Sounds like a great idea! If we do this right it will effectively make fantasy-land as expressive as static-land while keeping all use-cases of fantasy-land. Here is how we could do this:
It's important to not require values to have
This will also make life easier for implementers and specs maintainers, and unite the community around single spec! @joneshf You've mentioned couple times in chat room that we should merge specs, very curious on your opinion on this. |
I'm down to try whatever, especially if it brings FL and SL closer to merging! I don't really understand how the suggestion makes all those things possible, but I trust you all do 😊 |
I don't understand this. If values only may have it then one can't rely on values having one.
Can't we get that and still require one of them to be defined at a specific location? |
A type may support only static-land style spec or both static-land and fantasy-land styles. It would be the same situation as currently with two specs: some types can support both, some only static-land. If you support both you get more, like interoperability with Ramda. But if you can't add
Yeah, no problem with that. One of them can be a default. |
I wondered about "may" also. It may be good for the spec (if this idea ends up being adopted) to provide guidance/clarification around this (like you just did :) ), perhaps even strongly recommending the default location for the type representative when it's possible. |
Hi everyone, Nice to see some good propositions like this one. Here are my two cents on the issues from briancavalier's first post:
This article is somewhat related to the discussion and it is an interesting read: |
@boris-marinov: #1 does not fly if people are not using for instance I define my ADTs like: function IO(run) {
const map = fn => ...
...
return { run, map, ap, chain, of}
} so using |
@boris-marinov The point of this proposal is to unite fantasy-land and static-land. Static-land has some advantages over fantasy-land (see pros in the docs and previous comments here). If we do this right fantasy-land will also support cases that only static-land supports without loosing anything. And we will have only one spec. That's why we need type representatives to play the central role instead of using prototypes. |
I get that. What I was saying is that an object can be defined by functions that use the const array = {
map (f) {
return array.reduce.call(this, (acc, el) => {
acc.push(f(el))
return acc
}, [])
},
reduce(f, acc) {
for (let i = 0; i < this.length; i++) {
acc = f(acc, this[i])
}
return acc
},
chain (f) {
return array.reduce.call(this, (a, b) => a.concat(f(b)), [])
}
} Using with point-free styleAs you'd imagine, its not hard to convert a function that uses const curryThis = (func) => (...args) => {
if(args.length !== func.length) {
throw new TypeError('Wrong number of arguments')
} else {
return (data) => func.apply(data, args)
}
}
const curryAll = (functions) => Object.keys(functions)
.reduce((curriedFunctions, key) => {
curriedFunctions[key] = curryThis(functions[key])
return curriedFunctions
}, {})
let { map, reduce, chain } = curryAll(array)
let compose = (f, g) => (a) => f(g(a))
let doStuff = compose(chain((a) => [a, a]), map(String))
console.log(doStuff([1, 2, 3])) //['1', '1', '2', '2', '3', '3']
map((a) => a, (a => a)) //TypeError('Wrong number of arguments') Using with OOPCreating objects from these type definitions is trivial for non-built in values.Built-ins can also be used with the type descriptor, by using underscore-style wrappers: const wrap = (functions) => {
const proto = Object.keys(functions).reduce((proto, key) => {
proto[key] = function(...args) {
//Just a sample implementation
//Obviously not all functions return the same type of object as they accept
return constructor(functions[key].apply(this.value, args))
}
return proto
}, {})
const constructor = (value) => {
let object = Object.create(proto)
object.value = value
return Object.freeze(object)
}
return constructor
}
let ArrayPlus = wrap(array)
console.log(ArrayPlus([1, 2, 3])
.map((a) => String(a))
.chain((a) => [a, a])
.value)
//['1', '1', '2', '2', '3', '3'] Using with the ES7 Bind operatorThe best part is that this prototype format will instantly be compatible with the new bind operator. let { map, reduce, chain } = array
[1, 2, 3]::map((a) => String(a))::chain(a) => [a, a]) That is what I had in mind. |
Ah, I see. Yea, bind is cool. Although it's only stage 0. We could also write a converter from the current static-land approach to the bind compatible one: const bindify = T => {
return {
map(f) {
return T.map(f, this)
},
...
}
}
let { map, reduce, chain } = bindify(array)
[1, 2, 3]::map((a) => String(a))::chain((a) => [a, a]) Other way around conversion is also possible of course, so the question is what to choose for default. To me current static-land approach seems like a better default so far, but need to think more about it. |
One thing that you should consider is that you cannot have a generic converter from static-land-style type representative to a bind-compatible one, while you can have the reverse. |
Also one issue I see in FL is that we can't define typeclass which is parameterized with multiple types, Kleisli Functors for example: class Monad m => KleisliFunctor m f where
kmap :: (a -> m b) -> f a -> f b |
🤔 On Thu, 3 Nov 2016, 12:37 Irakli Safareli, [email protected] wrote:
|
static method like this maybe good for simple usage of monad, but when you do more than that, say a monad transformer then good luck for user to give correct dictionary for it. |
@syaiful6 Can you give an example? I don't understand monad transformers very well yet, but as far as I understand transformer just takes a monad and gives you another monad with some added effect. So can it be just a function that takes one Type Representative and returns another one? |
@rpominov Yes, to define the monad maybe simple, but not all. If you try to define a free monad transformer, maybe to support side effecs for your dsl, then you will require 2 dictionary, one for monad (the effects) and one for the functor (the command). The writer of monad transformer can just define the free monad, the problem will be more complex when another author use the free monad tranformer for their library, maybe a coroutine. for simplicity, purescript coroutine: here you will require more than 3 all dictionary (2 for functor, 1 monad) - probably more, the order should happen correctly. and the value inside the monad transformer should be correctly the associated with dictionary and value you given on it. if we just use the current fantasy land, you will just require 1 monad dictionary passed on it, which you can require on top of the module. const fuseWith = curry(3, (f, fs, gs) => {
return freet.FreeT(() => go(Tuple(fs, gs)))
function go(v) {
let n = ap(
map(
liftA2(f(Tuple)),
parallel(freet.resume(fst(v)))
),
parallel(freet.resume(snd(v)))
)
return chain(next => {
return next.matchWith({
Left: ({ value }) => pure(M, Left(value)),
Right: ({ value }) => pure(M, Right(map(t => freet.FreeT(() => go(t)), value)))
})
}, sequential(n))
}
}) |
I agree that this seems hard to implement using only Type Representatives, i.e. implement But this proposal suggests to not only have Representatives but also to have "fantasy-land" property on each value that contains corresponding Representative. With that in mind I don't understand why this example will be harder compared to current fantasy-land. I mean we still can write a function map(f, v) {
return v["fantasy-land"].map(f, v)
} And then use such |
Ah, i got it. I think this suggest us to move all function to static one. It's ok i think if we have both. but it would require the method happen on two locations? on the prototype and the type? what a pain to write the ADT, think ADT sum type. but it look like good to try, since it have some advadtage. |
Actually monad transformers are even simpler to implement with type representatives: Type representatives allow you to change the type of a given value without wrapping it in a container and the abundance of containers is one of the biggest downsides of Monad Transformers. So for example the type of newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } where the sole purpose of the MaybeT container is to allow for instance (Monad m) => Monad (MaybeT m) where
return = lift . return
x >>= f = MaybeT $ do
v <- runMaybeT x
case v of
Nothing -> return Nothing
Just y -> runMaybeT (f y) Using type representatives, maybeT can be implemented using just (no pun intended :)) one value of type // (val) => M({value:val})
of (val) { return this.outer.of({value: val, isJust:true }) },
// (val => M({value:val}) , M({value:val})) => M({value:val})
chain (funk, mMaybeVal) {
return this.outer.chain((value) => {
return value.isJust ? funk(value.value) : this.outer.of(value)
}, mMaybeVal)
}, The rest of the code is here |
No, I guess if we go with this approach methods will live only on Representatives. |
@rpominov then how the value have "fantasy-land" property without usage of prototype and somehow should be sync on the method live on Representative, right? like Just/Nothing on Maybe? @boris-marinov i know it simple to define the monad transformer, see my comment above. The problem i see it when use it on another library. I agree this proposal have some advantages and solve some of current spec i think. I aggree with this propopsal, as long as the value also have "fantasy-land" property on it, and not go static like Static Land spec. |
Sorry, I may have misunderstood the question. function Id(x) {
this._x = x
}
const IdRepresentative = {
of(x) {
return new Id(x)
},
map(f, v) {
return new Id(f(v._x))
},
}
Id.prototype['fantasy-land'] = IdRepresentative
// No this stuff any more
// Id.prototype['fantasy-land/map'] = function(f) {...} |
@rpominov yes, i think i confused about the term TypeRepresentative, when i read the spec it suggest me it the constructor.
but then here i think it referred on something else. |
Yeah, "Type Representative" in current fantasy-land and in this proposal have slightly different meaning. In current specIt's an object like: {
'fantasy-land/of'(x) {...},
'fantasy-land/empty'() {...},
'fantasy-land/chainRec'(f, i) {...},
// that's it, only 3 methods
} That lives on In this proposalIt's an object like: {
of(x) {...},
empty() {...},
chainRec(f, i) {...},
map(f, v) {...},
ap(f, v) {...},
... // all the rest of the methods also should be here
} That lives on |
that make sense now, i think this proposal want the fl methods defined on constructor, which i think just like static land, since it will not available on the instance. Can we still type it using typescript/flow? is it possible to write it the ADT using these typed js? i dont have strong opinion on it, just many people asks the type definitions for some ADT. what's other advantages of this proposal beside the single location of the method? if it just location, then what's really we gain because of it? |
Not sure about types yet, but I think situation may actually improve compared to what we have currently with fantasy-land, or at least not get worse. Writing typings for individual types should be possible. But representing something like type classes in Flow/TypeScript is still tricky, but also may become easier with this approach (I guess flow-static-land may be compatible with this new approach). @gcanti may have a better perspective on this.
This can basically unite two specs. The key is to not require to have |
@rpominov sorry, i am with @paldepind comment above and doesn't understand it even after reading your comment. but, just go with this proposal. it would make sense to see the actual PR i think. |
If you're reading through discussion, please read this comment. This one is important.I think we should just make a distinction between spec compatible Type Representatives and spec compatible values. Spec compatible Type Representative is just a dictionary with certain functions, for which certain laws stand, for example And spec compatible value is a value that has a Having this two separate artefacts, people may choose to totally ignore values part and write their generic code against Type Representatives. Or they may use values part as well. Choosing one way or another has certain trade-offs described above. One important detail: Update:
But probably not in the case when we get Representative from a value. In other words this should not be allowed Udate 2: See example https://gist.github.com/rpominov/6b4462137aff8de92dbd078da6a3564c |
@rpominov I'd be glad to help with the Flow typings, but I don't understand how all this should work. Perhaps a concrete implementation example, let's say |
Another observation: even with the proposed changes I don't see how to encode in fantasy-land the following two monoids
without modifying the |
The idea, I think, is to have a the type definition as a separate argument, so for example an implementation of fold would be: let fold = (MonoidA) => (listA) =>
listA.reduce((a1, a2) => MonoidA.concat(a1, a2), MonoidA.id())
fold(sumMonoid)([1, 2, 3, 4])) //10
fold(multiplicationMonoid)([1, 2, 3, 4]) //24 I will leave another link to this article, in case you missed it, because I think it talks about the same thing: Here are the monoid instances: const sumMonoid = {
id() {
return 0
}
,concat(a1, a2) {
return a1 + a2
}
}
const multiplicationMonoid = {
id() {
return 1
}
,concat(a1, a2) {
return a1 * a2
}
} |
@boris-marinov that is how static-land actually works. But what about fantasy-land? If we admit explicit dictionary passing, then we can just drop fantasy-land for static-land |
@gcanti The idea is to unite the two, have a single spec that allows both approaches. See #199 (comment) and also #199 (comment) #199 (comment) |
Created some examples here https://gist.github.com/rpominov/6b4462137aff8de92dbd078da6a3564c |
@rpominov That would be great, though from the discussion I'm not sure how. Something like this? // @flow
const maybeFunctor = {
map<A, B>(f: (a: A) => B, fa: Maybe<A>): Maybe<B> {
return fa instanceof Nothing ? fa : new Just(f(fa.value))
}
}
class Nothing {
static fantasyLand = maybeFunctor
map(f) {
return maybeFunctor.map(f, this)
}
fantasyLand() {
return maybeFunctor
}
}
class Just<A> {
value: A;
static fantasyLand = maybeFunctor
constructor(value: A) {
this.value = value
}
map<B>(f: (a: A) => B) {
return maybeFunctor.map(f, this)
}
fantasyLand() {
return maybeFunctor
}
}
type Maybe<A> = Just<A> | Nothing;
const double = n => 2 * n
const x: Maybe<number> = new Just(1)
// 4 ways to map
x.map(double)
maybeFunctor.map(double, x)
x.fantasyLand().map(double, x)
x.constructor.fantasyLand.map(double, x) EDIT: ah sorry, I didn't see your last comment before submitting |
Yeah, something like this. Pity that Flow doesn't allow
Same issue with current fantasy-land facebook/flow#2482 |
I'd like to provide a different point of view, hopefully alleviating some of the confusion in this thread, and introducing a new idea: Static Land can have multiple Type Representatives for the same value, which is great; for example we can do The latter is a problem. It forces us to pass around the Representative along with the value every time we call a function which needs to know the Type (and its implementation). Fantasy-Land solves this problem by associating the Representative with the value itself. In v1, values pointed to their Representative through the
The inconvenience in both cases, however, is that we're sharing a name-space with other stuff. Whether it be the method namespace or static namespace. In order to combat this issue we've prefixed every property which is part of the Type Representative, so it can live in harmony with whatever else lives there. Now we're looking forward to change the location of the type-representative once again, and finally to a destination where the properties don't have to share. What we end up with are Representatives which are completely compatible with Static Land Representatives. It's like we're using Static Land, but giving values some knowledge about their Representatives by association through the A more interesting question, to me, is whether we can encode the multiple Type Representatives thing in a useful way. We're stuck in associating a single Type with every value, because that's how we started. But maybe we can do something useful if we associate all types of a value with it; say: Edit: I should clarify that the above approach probably won't gain us anything, but it does demonstrate an idea which flows forth out of the point of view I'm trying to get across, where Fantasy Land merely specifies a way to associate values with their Type(s). |
@gcanti, in your example Isn't it possible to add the type param also to |
There're some discussions going on in static-land's repository, that may be related to this proposal. Would very appreciate any input there! |
To move this forward I've created a separate repo where we can discuss specifics of the new spec in individual issues. Hopefully nobody minds. Otherwise this issue will became too big. https://github.com/fantasyland/unified-specification I'll open couple issues there soon. |
I'd rather you forked fantasy-land and then done a PR imo. |
I would love to, but actually I'm not sure I'm good for the task. I think this'll require basically a rewrite from a whole different perspective. And having wrote static-land I'm not sure I like the result. My english is mediocre, and education in some areas related to the specification also not great. So not sure I should do this, but I want to help in other ways. Also it feels like we need more discussion on the specifics before any PR. And it won't be manageable to do all of them in this issue, or inside a PR. So I thought having a temporal repository to manage this process is a good idea. |
Fair play, continue ;-) nothing to see here.
…On Sun, 27 Nov 2016, 17:38 Roman Pominov, ***@***.***> wrote:
I would love to, but actually I'm not sure I'm good for the task. I think
this'll require basically a rewrite from the whole different perspective.
And having wrote static-land I'm not sure I like the result. My english is
mediocre, and education in all areas related to the specification also not
great. So not sure I should do this, but I want to help in other ways.
Also it feels like we need more discussion on the specifics before any PR.
And it won't be manageable to do all of them in this issue, or inside a PR.
So I thought having a temporal repository for this is a good idea.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#199 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ACcaGM0hHVLUapdTzVOOTM-rU3YZshTbks5rCcAogaJpZM4KkRUs>
.
|
Hi all. Could you please vote on this? In theory, if I'd create a PR for this, would you prefer it to has minimal amount of changes, or would bring most of static-land with itself? 🎉 for as minimal changes as possible, Note that I've worked a lot on the structure and terminology of static-land, and in my opinion it would make sense to use what I've ended up with. If we try to preserve that of current Fantasy Land document the result might be confusing and harder to understand. Also note that if you vote I will not automatically expect of you to support the PR, just want to know what you think has a higher chance of moving forward. And I probably won't open a PR right away, there will be a temporary fork, which we'll be able to polish before an attempt on final merge. I just want to understand what is better direction in your opinion. |
I've started to prepare repository for a PR here https://github.com/rpominov/fantasy-land. Feel free to open PRs in it for any changes you want to be made before the big PR to the main repository. Ideally, in the final PR, we should discuss only whether we want it to be merged, and decide on all details before. |
Further to Roman's point, please add yourself as a watcher of rpominov/fantasy-land if you're interested in following the preliminary work and making suggestions. :) |
@rpominov Unless I'm mistaken it's impossible to create issues in rpominov/fantasy-land. Is that intentional? |
@paldepind Sorry, missed your comment. I've turned on issues now, it's probably off by default for forks. |
I'll probably won't be doing any changes to the repository unless something comes up. But I'm going to wait for any PRs or issues for some time before opening the big PR. |
Going to open the PR in couple days. I figured opening the PR is not a big deal, we'll still be able to discuss and make changes in the fork, and PR will be updated automatically. |
Recently, I started updating two libs (creed and most) to support FL 2.0, and added Static Land support to most. In the process, I read this thread that led to standardizing on Type Representatives for static functions like
of
andempty
.All of that led me to the thought of leveraging Type Representatives more fully. I wanted to put the idea out for discussion to see if it seems interesting:
Rather than many namespaced methods, e.g.
x[fl.map](f)
, FL could move to functions on a single namespaced Type Representative, e.g.x[fl].map(f, x)
, wherex[fl]
is the location ofx
's Type Representative, andfl
is the only required FL string/Symbol.This is obviously a substantial change, but I think it would have several benefits:
.constructor
as the location of the Type Representative. Although, it may also be worth considering whether it's useful to add the Type Representative asConstructor[fl]
for cases where where no instance is available.I don’t think this has many downsides compared to the current FL 2.0 approach. For example, ergonomics: the prefixed methods required by FL 2.0 are, imho, already fairly unergonomic for app devs. Library devs will endure them in order to get the benefits of being FL compliant. Moving to functions on a single namespaced type representative seems like it retains the same level of ergonomics.
Any FL delegation library such as fantasy-sorcery or Ramda would need to change its dispatching, which would be a breaking change, but the dispatching is no more complex.
What do you think of the idea of having all functions on a single namespaced Type Representative?
The text was updated successfully, but these errors were encountered: