Skip to content
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

Folding has no explicit function #95

Closed
2 tasks done
rstegg opened this issue Feb 28, 2017 · 12 comments
Closed
2 tasks done

Folding has no explicit function #95

rstegg opened this issue Feb 28, 2017 · 12 comments

Comments

@rstegg
Copy link
Contributor

rstegg commented Feb 28, 2017

updated to @evilsoft's reply, add a fold function:

  • to List crock (List f, Semigroup s => f s ~> () -> s)
  • to pointfree. (fold : Foldable f, Semigroup s => f s -> s)
@evilsoft
Copy link
Owner

evilsoft commented Feb 28, 2017

Alrighty, going to redefine this after thinking about it for a second.

I would be nice to have a way to fold a List or Array of semigroups down and extract from the container.

As folds are called reduce in JS, I am thinking we should add a fold function to List that will fold down a list of Semigroups and extract the Semigroup from the List, as folds do. Also this will create the need for a fold pointfree function that can accept any foldable structure. So you can do things like:

const {
  Pred, compose, contramap, fold,
  map, isNumber, prop, option,
  reverseApply, runWith
} = crocks

// defProp : a -> String -> Object -> a
const defProp = curry(
  (def, key) => compose(option(def), prop(key))
)

// defNotFound : String -> Object -> String | a
const defNotFound =
  defProp('Not Found')

// preds : [ Pred Object ]
const preds =
  map(reverseApply(Pred(isNumber)), [
    contramap(defNotFound('a')),
    contramap(defNotFound('b')),
    contramap(defNotFound('c'))
  ])

// buildValidator : [ Pred a ] -> b -> Boolean
const buildValidator =
  compose(flip(runWith), fold)

// isValid : a -> Boolean
const isValid =
  buildValidator(preds)

isValid({ a: 34, b: 10, c: 3 }) // true
isValid({ b: 10, c: 3 }) // false
isValid({ a: 34, b: 'ten', c: 3 }) // false
  • Add fold to List crock (List f, Semigroup s => f s ~> () -> s)
  • Add fold pointfree. (fold : Foldable f, Semigroup s => f s -> s)

@rstegg rstegg changed the title Add concatList to take a foldable of same semigroup Folding has no explicit function Mar 1, 2017
@evilsoft
Copy link
Owner

evilsoft commented Mar 6, 2017

Submitted PR#101 to address this issue.

@rstegg
Copy link
Contributor Author

rstegg commented Mar 7, 2017

What are your thoughts on a foldWith for completeness?

List([1,2,3,4,5]).foldWith(add2) // [3,4,...]

implementation would be something like:

  List m => m a ~> (a -> b) -> b
  function foldWith(fn) {
    if(!isFunction(fn)) {
      throw new TypeError('List.foldWith: Function required')
    }

    return xs.map(fn) 
  }

@rstegg
Copy link
Contributor Author

rstegg commented Mar 7, 2017

I realize this is adding a lot to a single type; but doing some 👀 around, I found that List is very "immutableJS" territory, see: https://facebook.github.io/immutable-js/docs/#/List [i did not really look into immutablejs until now. if you are already aware of the implementation which immutable provides, please ignore this]

@evilsoft
Copy link
Owner

evilsoft commented Mar 7, 2017

@rstegg That is a valid point. I thought about adding foldMap. And really foldMap in its implementation is really just reduce. Notice how you are returning an array, which is just the underlying implementation of List in its current state. So because it is maintaining structure, all you really did there is implement map and extract to an Array, which IMO does not add value. A fold as I understand it, take a collection (one or many) applies some function to it, to combine all elements into a single value (usually). Which is what reduce is in JS.

The reason why we do not need a combining function for this fold is because we have the Semigroup constraint on the underlying values. So we will use the built in function concat which is on every semigroup to do the combination.

What you implemented can easily be expressed as a function to reduce that uses an Array as its initial value, and concats each item in the List to it. I really do not see the need to add this as it may be confusing to have this option. But I am open to argument on this one.

@evilsoft
Copy link
Owner

evilsoft commented Mar 7, 2017

I would say that List is the domain of all functional libs and paradigms. It is the fundamental/canonical representation/expression of determinism we have. Now one thing my List lacks (that will be rectified soon) is the constraint that all elements MUST be of the same type. Once we have that I would say that this List can prove to be very useful.

I am curious what would be gained by providing that robust API? A lot of those functions make sense in an imperative environment (shift/unshift as to cons/head/tail) . And with the improvements to Array based functions, you should be able to get those more imperative bits with your standard JS Array.

@evilsoft
Copy link
Owner

evilsoft commented Mar 7, 2017

Merged PR#101. Will go out on next release.
Will close after conversation resolved.

@evilsoft
Copy link
Owner

evilsoft commented Mar 7, 2017

Now that said, your provided implementation does not add much value.
Now if we had a Semigroup constraint on the return of the Mapping function THEN we are in business IMO.

List m, Semigroup s => m a ~> (a -> s b) -> s b

Then we could do things like:

const { List, Sum, foldMap, concat, reduce } = crocks

const ar =
  List
    .fromArray([ 1, 2, 3 ])
   .foldMap(Sum)
// => Sum(6)

What do you think about that?

@evilsoft
Copy link
Owner

evilsoft commented Mar 7, 2017

While that seems like a good idea one thing that comes to mind is that we already have mconcat and mconcatMap which already takes either a List or Array (for this exact use case) so we can do this to get the same thing as above:

const { Sum, mconcat } = crocks

const data =
  [ 1, 2, 3 ]

const sumList =
  mconcat(Sum)

sumList(data)
// => Sum(6)

sumList(List.fromArray(data)
// => Sum(6)

The only bummer, is that mconcat and family need to recognize String and Array as proper Monoids that way you can make single level flatten like:

const data =
[ [ 1, 2], [ 3 ], [ 4, 5 ] ]

const flatten =
  mconcat(Array)

flatten(data)
// => [ 1, 2, 3, 4, 5 ]

@evilsoft
Copy link
Owner

evilsoft commented Mar 7, 2017

I think we would get better reusability if instead, we just focus on getting the mconcat family to fully honor the Monoidal qualities of Array and String.

@rstegg
Copy link
Contributor Author

rstegg commented Mar 8, 2017

All good points, in my opinion I like not having to wrap my data in an array (e.g. List(1,2,3,4) => List [1,2,3,4]), because that just, to me, feels like an extra thing to remember about this implementation of List and one that I really think is great in immutablejs.

Back on foldMap, that is correct I did not think of how many ways there could be to solve one problem, and adding another solution should seem like a waste of time.

I think this has solved any problems we've had with fold now, 👍 to closing this issue.

@evilsoft
Copy link
Owner

evilsoft commented Mar 8, 2017

Well typically, you build a List out by consing, or generating it, then fold it down in to a result. So passing an Array into it, is just for convince if you NEED to be in a List. Soon, we will have a lot of functions that just work on Arrays like any other datatype in crocks.

Closing!

@evilsoft evilsoft closed this as completed Mar 8, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants