Comments (5)
I would love fmap for js; I would love a proper algebraic data types setup for js even more!
As you have noticed, another good name for ferrum might be haskelllish-js! Besides the name being to cumbersome and the obvious PR advantages, there is another reason why it's not called that: Ferrums soundness goals. This is really one of the main features from rust ferrum tries to introduce: Every feature should be hard to abuse and should be relatively edge case free. You can see this in functions like type() or typename() which explicitly deal with null. Or in trySomething
styled function which take a default parameter, making you choose which value to return instead of just returning null or undefined (enabling the None = Symbol(); v = trySomething(param, None); if (v === None) …
error handling idiom.
There was a fairly decent attempt utilizing coroutines to introduce something akin to the do …
notation from haskell: https://medium.com/flock-community/monads-simplified-with-generators-in-typescript-part-1-33486bf9d887
The approach works, but suffers from soundness issues which I outlined here: https://gist.github.com/koraa/78ee4620a5842ede4c62dd65708326c7
I also have reservations to just introducing fmap; it's existence has to be explained to users (why not unify with map() – this is a valid concern in haskell too where the difference between map and fmap is historic as it is here), it also wouldn't be very complete (you got .then but what about .catch? another trait for that?). Should map()
try conversion to iterator first or use fmap()
directly? Maybe would represent yet a fourth way of representing null-results (besides the null, undefined and the None-idiom). JS devs would as: "But what is wrong with null". The answer is a lot, but it is hard to explain and confusing to integrate with null/undef. Why introduce Either? JS is duck typed? I see the appeal, most js devs wont.
Ferrum comes with ifdef(val?, (v) => v)
and isdef(val?)
which are useful for dealing with null/undefined in pipes which substitute for Maybe. We could also provide default(val?, def), defaultWith(val?, () => def)
– replacing null with a default value. I am considering then(Promise, fn), reject(Promise, fn), finally(Promise, fn)
as curryable functions on promises, as well as resolve(val)
as a free-standing promise constructor. Potentially also free standing all()
and race()
functions.
Note these soundness/clarity issues shouldn't stop you from implementing a functor trait. It is perfectly feasible with ferrum, just hard to communicate…
I am planning however to introduce a foundational sequence trait, probably using coroutines; there are a lot of open questions and I am not even sure it is possible, but I could imagine this might work for some monadic types too…
const Begin = Tuple("Begin", seq);
const Gather = Tuple("Gather", it);
const Provide = Tuple("Provide", val);
const EndOfSequence = Tuple("EndOfSequence");
// Implement for sequence/iterator AND async sequence; extendable for reactive/event streams
const AbstractSequence = new Trait("AbstractSequence");
// Implement methods
const map = abstractGenerator(function*(seq, fn) {
const it = yield Begin(seq);
while (true) {
// How does that even work?
const v = yield Gather(it);
if (v === EndOfSequence) break;
yield Provide(fn(v));
}
});
const filter = abstractGenerator(function*(seq, fn) {
const it = yield Begin(seq);
while (true) {
// How does that even work?
const v = yield Gather(it);
if (v === EndOfSequence) break;
if (fn(v)) yield Provide(it);
}
});
And some very limited control flow for pipe()
would be nice so you could pipe(…, await, …)
and pipe(…, ifdef, …)
.
from ferrum.
Thank you for the very detailed response!
Ferrum comes with ifdef(val?, (v) => v) and isdef(val?) which are useful for dealing with null/undefined in pipes which substitute for Maybe.
This is a very good point. I am a fan of composing curried and point free functions. This is nice and lightweight -- handles nicely without the extra baggage of mimicking pattern matching.
I am considering then(Promise, fn), reject(Promise, fn), finally(Promise, fn) as curryable functions on promises
Also a fascinating idea.
... implementing a functor trait. It is perfectly feasible with ferrum, just hard to communicate…
Yes. And the deeper I go down the rabbit hole, I can see how much deeper it is :)
I've been trying a few different things, and I've gotten a bit stuck on the convention that creating a new Trait only provides a single invoke
function. Some traits require implementation of multiple functions. To invoke a trait, though, only provides a single function per implementation. This is okay for those Traits that only require one function, but is confusing for those with more. A ferrum Trait, is less like a Rust trait, and more like a more specific contract on a single behavior.
To combine the concept of Sequence
's map and a functor's fmap, I don't think one should create a ferrum Functor
trait. Each implementation actually needs it's own definition of map
/fmap
, so the ferrum Trait should maybe be more like a specific FMap
trait. This is kinda trivial for just Functor, so consider Bifunctor instead, where bimap
, first
, and second
, etc. functions could all be defined for each implementation.
So, one could take the path of creating BiMap
, First
, and Second
ferrum Traits. This technique is kind of embodied in the stdtrait.js
definitions. This does sound counter to the idea of a "Trait", but it makes using this library very straightforward.
On the other hand, I've been looking at how a trait could be implemented by providing a thunk to an object with several function definitions, so that multiple function definitions must be provided at the same time.
Also of note: I love the way implementations can be derived! If there were an fmap
/map
Trait, than it could be implemented automatically for Sequence
(though I have not exhaustively tested it).
// FMap Trait Definition
// Could be called "Mappable" instead? for the hardcore FP uninitiated?
const FMap = new Trait('FMap')
const fmap = curry('fmap', (what, f) => FMap.invoke(what, f))
// Derive FMap for Sequence
FMap.implDerived([Sequence], function* ([iter], seq, f) {
for (const val of iter(seq)) {
yield f(val)
}
})
const sequenceTest = pipe([1, 2, 3, 4], fmap(plus(10)), list)
// [11, 12, 13, 14]
easy enough to implement for a trivial Option type, for example
// Option
const Some = function (value) {
this.value = value
}
const None = function () {}
const none = new None()
FMap.impl(Some, (what, f) => new Some(f(what.value)))
FMap.implStatic(none, () => none)
const someTest = pipe(new Some(2), fmap(plus(4))
)
// Some { value: 6 }
const noneTest = pipe(none, fmap(plus(4)))
// None { }
For a bifunctor, I tried tried out implementing multiple functions under a single trait. While it does the job, it's not very nice, and I'm sure I am breaking the ability to do other things within ferrum.
// Bifunctor
const Bifunctor = new Trait('Bifunctor')
const bimap = curry('bimap', (what, f, g) => Bifunctor.invoke(what).bimap(f, g))
const first = curry('first', (what, f) => Bifunctor.invoke(what).first(f))
const second = curry('second', (what, g) => Bifunctor.invoke(what).second(g))
// Result
const Ok = function (value) {
this.value = value
}
const NotOk = function (value) {
this.value = value
}
FMap.impl(Ok, (what, f) => new Ok(f(what.value)))
Bifunctor.impl(Ok, (what) => ({
bimap: (f, g) => new Ok(f(what.value)),
first: (f) => new Ok(f(what.value)),
second: (g) => new Ok(what.value),
}))
FMap.impl(NotOk, (what, f) => new NotOk(what.value))
Bifunctor.impl(NotOk, (what) => ({
bimap: (f, g) => new NotOk(g(what.value)),
first: (f) => new NotOk(what.value),
second: (g) => new NotOk(g(what.value)),
}))
const resultTester = (x) => pipe(x,
bimap(plus(5), (e) => `Major ${e}`),
first(plus(40)),
second((e) => `${e}, Seriously`)
)
const okTest = resultTester(new Ok(5))
// Ok { value: 50 }
const notOkTest = resultTester(new NotOk('Error'))
// NotOk { value: 'Major Error, Seriously' }
from ferrum.
I'll also say that going into this, I did not realize that Rust does not define functor/monad operations through traits. Where there is overlap, the types just implement similar functions based on convention. Rust comes out okay without a generic fmap.
I am thinking of dropping an concern for implementing traits for functors, et al.. But I still love how traits can be used for all the other use cases!
from ferrum.
Fascinating! Thank you for sharing the code!
The idiom you are using – returning some object that implements an interface – is exactly what I would recommend for implementing more complex interfaces. It's the same idiom used by the iterator protocol.
Be careful with trait derivations; they are kind of icky, using a O(n)
lookup…not a problem if you just have a small number of derivations though.
Btw, your named tuple implementation suffers from some drawbacks…e.g. will act weirdly if not invoked with new…which is why deferred to some "imaginary" future tuple constructor. If you want tuples now I recommend something like this:
function Ok(value) {
if (isdef(this) || type(this) !== Ok)
return new Ok(v);
Object.assign(this, { value });
}
Ok.protoype[Symbol.iterator] = () => iter([this.value]); // Destructuring
from ferrum.
Just interested: What are your thoughts on #138?
from ferrum.
Related Issues (20)
- Tree traversal functions
- ferrum.async submodule for working with promises
- TryGet trait (Get() with default value)
- Add tests making sure that all functions can be included in es6 modules.
- Custom struct infrastructure HOT 1
- lint examples
- Fill in missing documentation HOT 1
- null/undefined safe ways of working with properties & entries
- composev()
- Move all code inside their own (javascript) submodules.
- All types should support the visitor pattern used by hash()
- ferrum.heuristic companion library
- anyof, allof syntax for multiline boolean value expressions
- Extend the comparisons in README.md to Ramda HOT 2
- Extend Ferrum to work with async sequences HOT 2
- Take is broken on plain arrays? HOT 2
- Test each function in sequence with lists and sequences
- Dependency Dashboard
- HTTPS Error on www.ferrumjs.org
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ferrum.