Resolve constructor defaulting
// Style :: => (Props -> CSS) -> Style CSS
function Style(f) {
if (!(this instanceof Style)) {
return new Style(f);
}
this.__value = typeof f === "function" ? f : () => f;
}
i'm not a fan of this defaulting. IMO either always take a function or change the type sig to describe what legal arguments are (namely anything) @buzzdecafe
I think this is due to .of —
Would this be better @buzzdecafe?
// Style :: => (Props -> CSS) -> Style CSS
function Style(f) {
if (!(this instanceof Style)) {
return new Style(f);
}
- this.__value = typeof f === "function" ? f : () => f;
+ this.__value = f;
return this;
}
// .of :: Applicative a -> Style a
Style.prototype.of = function(a) {
- return new Style(a);
+ return new Style(_ => a);
};
So this depends on if we want to support Style.of({ foo: "bar" }) or require Style.of(_ => ({ foo: "bar" })). To keep with fantasy-land spec, we can't check the value when using of:
No parts of
ashould be checked (from spec).
This is true for static land checks
I'd rather change the type signature to be accurate 👍
I don't understand "No parts of a should be checked"
I was imagining this similar to fluture's of - i.e. creating a Style that always resolves to the provided value, ignoring the props you resolve it with:
.of :: a -> Future _ a
Creates a Future which immediately resolves with the given value. This function is compliant with the Fantasy Land Applicative specification.
const eventualThing = Future.of('world');
eventualThing.fork(
console.error,
thing => console.log(`Hello ${thing}!`)
);
//> "Hello world!"
@jongold I was reading the spec saying the .of method can't do the checking. But the constructor can?
Like https://github.com/fluture-js/Fluture/blob/master/fluture.js#L136-L143
So it looks like future.of returns a FutureOf which implements Future. Can you call Future.of(_ => a)?
If not, Style.of could only take an object and Style() could only take a function? Using the same logic.
That was the behavior I was imagining, yeah.
The ramda-fantasy Future.of implementation is simpler —
// applicative
Future.of = function(x) {
// should include a default rejection?
return new Future(function(_, resolve) { return resolve(x); });
};
Perfect! I had that originally haha. Can do!
On Wed, Mar 15, 2017, 1:40 PM Jon Gold [email protected] wrote:
That was the behavior I was imagining, yeah.
The ramda-fantasy Future.of implementation is simpler —
// applicative Future.of = function(x) { // should include a default rejection? return new Future(function(_, resolve) { return resolve(x); }); };
— You are receiving this because you commented.
Reply to this email directly, view it on GitHub https://github.com/jongold/st/issues/6#issuecomment-286821807, or mute the thread https://github.com/notifications/unsubscribe-auth/AEvMsA4RhZehB8_XA52Cnd3p0qNxOGaLks5rmCJ0gaJpZM4Mde4F .
i reserve the right to contradict myself. So here goes. I believe I was in error. I think the "lawful" approach is that the Style constructor can do the wrapping when necessary, as it is now more or less:
function Style(f) {
if (!(this instanceof Style)) {
return new Style(f);
}
this.__value = typeof f === "function" ? f : props => f;
}
Style.of however, should be constrained to always take a function props -> Styleand not rely on inspecting the contents of its arguments to do the right thing. Currently it passes through to the constructor, which inspects the guts. This means that Style.of({arbitraryObject})works, when it really should not. Whether it should be enforced by the system or not (i.e. type-check the argument to of) is an open question.
Oops, merged #8 before I saw that - I'm confused as to why we can't have Style.of only accept and object and not a function that returns an object?
of is crazy man. It's lawless (see https://www.schoolofhaskell.com/user/gbaz/building-up-to-a-point-via-adjunctions). But there's an intuition:
AnyTypeAtAll.of(anyValueAtAll).map(x => "I just mapped x") // AnyTypeAtAll("i just mapped x")
So the question is what works with map?
I am struggling with what the type of this thing is. Is Style a "container" for a function (Props) -> Style CSS? Or is Style the function itself? If it is the function itself, it makes sense to me that of could take arbitrary values and wrap them in a function, and Style is a type-alias for that function. If it is a "container" of a function (i.e. this._value is always a function), then it makes sense to me that of should always take a function.
so lemme take a whack at @drboolean 's formulation.
Case #1, Style is a container of a function
Style.of(g).map(f) I would expect Style(f . g)? Is that the intent?
Case #2, Style is the function
Style.of(x).map(f) I would expect Style(f x) this implies style may contain arbitrary values, i.e. this._value may not be a function. But it may!
I am struggling with what the type of this thing is
Me too, Style seems something like this
type Style = (props: Props) => CSS
where Props and CSS are not better specified. Based on the examples in the README they are something like
type Dictionary = { [key: string]: any }
type Props = Dictionary
type CSS = Dictionary
So type Style = (props: Dictionary) => Dictionary.
Further implements FantasyLand 1, FantasyLand 2, FantasyLand 3 compatible Semigroup, Monoid, Functor, Apply, Applicative, Chain, ChainRec and Monad
Note that if Style is (props: Dictionary) => Dictionary then it has kind * and writing Style a doesn't make sense. Style can't be a Functor, Applicative, Monad.
It's not even a Semigroup since, based on the example in the README, concat has the wrong signature
concat :: Style -> Dictionary -> Style
// should be
concat :: Style -> Style -> Style
i am still fighting some confusion.
of wraps its input in a function. This means that the type of Style is Style :: (Dict -> Dict), yes? So as Giulio says above, what does Style a mean? Where is the polymorphism if the sig is so specific?
This makes me wonder about the sigs for e.g. map and chain. If the type variables a and b do not have meaning what is Style a ~> (a -> b) -> Style b?
resolve is described as being Props -> CSS; and map is defined:
Style.prototype.map = function(f) {
return new Style(props => {
return f(this.resolve(props)); // resolve :: Props -> CSS
});
};
... since resolve is Props -> CSS, and Style is a container of Props -> CSS, then f must be CSS -> CSS, yes? Are CSS and Props aliases?
welp I missed a bunch of notifications, my bad @gcanti @buzzdecafe
let's presume that I messed up the types in the docs - the library works as I imagined, I just wrote the HM types wrong :)
you're right - Style is a container of a function that resolves to a dictionary of CSS Style (anyProps -> ResolvedCSS)
... since resolve is Props -> CSS, and Style is a container of Props -> CSS, then f must be CSS -> CSS, yes? Are CSS and Props aliases? yes.
// no knowledge of 'props', just manipulating a
// CSS object
// makeTheLogoBigger :: CSS -> CSS
const makeTheLogoBigger = inputCSS => ({
...inputCSS,
width: inputCSS.width + 100,
height: inputCSS.height + 100,
});
// let's really break this down for clarity
// logoFn :: props -> CSS
const logoFn = props => ({
backgroundImage: `url(${props.url})`,
height: 48,
width: 48,
border: props.outlined ? '2px solid hotPink' : 'none',
});
// logo :: Style (props -> CSS)
const logo = Style(logoFn);
in my intuition,
const logoBigger = logo.map(css => makeTheLogoBigger(css))
would still return Style (props -> CSS)
which we could then resolve like this:
const result = logoBigger.resolve({
url: 'placekitten.com/200',
outlined: true
})
/* {
backgroundImage: url(placekitten.com/200),
height: 148,
width: 148,
border: '2px solid hotPink'
}
*/
So I think these are (some of) the docs that are wrong, that I'll fix once we figure out what they should be
Style :: => (Props -> CSS) -> Style CSS
Style CSSis wrong here, my bad. I think it should beStyle (Props -> CSS)
.of :: a -> Style a
this is wrong - with our current model it throws away the props it receives but it's still
Style (Props -> CSS)
#concat :: Style a ~> Style a ~> Style a
should be
Style (Props -> CSS) -> Style (Props -> CSS) -> Style (Props -> CSS)?
the example in the readme is wrong
Style.of({ fontWeight: 'bold', fontSize: 14 }).concat({ fontSize: 16, backgroundColor: 'red' })
// should be
Style.of({ fontWeight: 'bold', fontSize: 14 }).concat(Style.of({ fontSize: 16, backgroundColor: 'red' }))
#map :: Style a ~> (a -> b) -> Style b
should be
Style (props -> CSS) ~> (CSS -> CSS) -> Style CSS
#chain :: Style a ~> Style a -> Style a
this just looks incomplete - my bad - I think it should be
Style (props -> CSS) ~> (CSS -> Style (props -> CSS)) -> Style CSS
Should I keep going / are we getting somewhere? My bad for messing up the docs; props to @jbaxleyiii for inferring what I meant not what I wrote :)
I'm still a little bit confused about the type I guess - is there a way to shorten Style (props -> CSS)? that was a lot of typing
Thanks @jongold , that addresses a lot of my confusion when trying to grok the docs. I think @gcanti 's comments still need addressing, though, especially:
Note that if
Styleis(props: Dictionary) => Dictionarythen it has kind*and writingStyle adoesn't make sense.Stylecan't be a Functor, Applicative, Monad.
Note that if Style is (props: Dictionary) => Dictionary then it has kind *
Re-reading the examples I think this is not true. For example
const makeTheLogoBigger = inputCSS => ({
...inputCSS,
width: inputCSS.width + 100,
height: inputCSS.height + 100,
});
inputCSS must be some subtype of { width: number, height: number } I guess. So the typings should be
export type CSSValue = number | string | ...
export type CSS = { [key: string]: CSSValue }
export type Props = { [key: string]: any }
export type Style<UB extends Props> = <P extends UB>(props: P) => CSS
So Style<A> is parametric.
From a theoretical point of view I think these typings can be justified considering another category (let's call it Sty) instead of JS, where JS is the usual category where
- objects are all usual types of JavaScript
- morphisms are all usual functions of JavaScript
Sty is (Props, Props -> Props) that is
- objects are all the dictionaries
- morphisms are all functions from a dictionary to a dictionary
Note that A in the definition of Style<A> is in contravariant position, so we can hope to find an instance of a contravariant (endo)functor, not a (endo)functor (just like React components).
A possible implementation in TypeScript
export type CSSValue = number | string
export type CSS = { [key: string]: CSSValue }
export type Props = { [key: string]: any }
export type Style<UB extends Props> = <P extends UB>(props: P) => CSS
export function contramap<A extends Props, B extends Props>(f: (b: B) => A, fa: Style<A>): Style<B> {
return <P extends B>(b: P) => fa(f(b))
}
type A = {
width: number,
height: number
}
const makeBigger = <P extends A>(a: P) => Object.assign({}, a, {
width: a.width + 100,
height: a.height + 100
})
console.log(makeBigger({ width: 100, height: 50, color: 'red' })) // => { width: 200, height: 150, color: 'red' }
function chooseColorFromWidth(a: A): A & { color: string } {
return Object.assign({}, a, {
color: a.width < 100 ? 'red' : 'blue'
})
}
const chooseColorAndMakeBigger = contramap(chooseColorFromWidth, makeBigger)
console.log(chooseColorAndMakeBigger({ width: 50, height: 100 })) // => { width: 150, height: 200, color: 'red' }
console.log(chooseColorAndMakeBigger({ width: 100, height: 100 })) // => { width: 200, height: 200, color: 'blue' }
Hope it helps
wow, this is super helpful - thanks so much @gcanti.