evilsoft / crocks

A collection of well known Algebraic Data Types for your utter enjoyment.
https://crocks.dev
ISC License
1.59k stars 102 forks source link

isJust & fromJust #450

Closed JonathanILevi closed 4 years ago

JonathanILevi commented 4 years ago

I do not see in the docs a isJust or fromJust (potential to throw but unless isJust was checked) nor isNothing. Even the pure Haskell has theme functions.

The primary importance (for me) is that values passed around are explicit but how I implement a function is not where errors arise. The fact that a value is a Maybe is obvious (or will raise obvious errors). My function should be able to deal with that logically without needing to get into callback functions (JavaScript does not even have do notation to make things at least look better). I give up compile time type safety/checking by using isJust and fromJust, but wait, JS is not statically typed anyway.

It is true I likely may never use isJust or fromJust and have the simple alternative with the other functions. But I am unwilling to start using a library without complete definitions (if I want to get a value from a Just (regardless of whether I theoretically should (even Haskell has performUnsafeIO!)) how can I do it?).

Thanks, Jonathan

dalefrancis88 commented 4 years ago

Hey @JonathanILevi, Thanks for the suggestion. I've seen things like isJust implemented by creating a func using either

const isJust =
  either(constant(false), constant(true))

for the fromJust i'd be hesitant in suggesting it's addition or usage. Maybe has two great options that you can use to define fallback values, option and alt. option allows you to fold out the value while specify the value you want to use if the Maybe is a Nothing. alt is for when you want to keep within the Maybe container and have an alternative Maybe value, if the original Maybe is a Nothing then the alt method will return the given Maybe otherwise it'll just return the current value inside a new instance of Maybe

Most of the time, if not all of the time, you don't want to think about it as needing to check if it is a Just or a Nothing you have your container that you can map or chain etc over and then fold it out when ready

evilsoft commented 4 years ago

@JonathanILevi Personally, I see those "theme" functions as bloat and while we use Haskell for some reference and provide some functions with the same name (fanout, fst, etc), we try not to let their choices influence what we provide in the library.

As far as your need for these functions, could you provide an example of where you would find them useful?

While it is a bummer that you feel this library is not complete without those two functions or theme functions, there are other libs out there like Sanctuary that do provide those functions and may feel more complete to you.

JonathanILevi commented 4 years ago

I totally agree with "most of the time, if not all of the time", but is it all the time or only most of the time? Unless you can mathematically prove that it is in-fact "all of the time" then the library is not turing-complete. I am actually sure that you can prove that you never need the fromJust but even if you can, do I need a masters degree in order to solve it, or could it cause a speed bottle neck (even the turing machine is turing complete)?

There are 3 places that I see fromJust potential being used:

  1. When I was first getting into the pure/functional style (with Haskell) I did use fromJust a couple times. Did I need to? no; I could go back now and obviously see how I should to it correctly. If I were forced to learn that then I might have given up. Having fromJust shallows the learning curve.
  2. Deep nesting. I had a piece of in Haskell where I had a chain of functions which alternated creating Maybes and IOs. I could not simply monad them together with >>= or even in a do block. I had to have nested blocks with sequences (Maybe IO a -> IO Maybe a), or invent some new funny monad thing, to get the desired end result. But, we are in JavaScript and we do not have good do notation or even function call notation without excessive parentheses.
  3. I never expect code that uses fromJust to expose the fact that it is doing so. In come functions, imperative style is just easier to read (or easier for some to think in). If a function has a complex nesting of maybes one might want to implement the function differently for one reason or another.

This Library is not a learning library? It is a library designed to be used. Do not force me how to think--do convince me though! (not literally me, I already am) Convince and teach in blog posts and books--specifications are a very bad teacher.

The primary reasoning is that, even if fromJust is never needed; noobish me used it because I did not know how to think the right way. Shallow the learning curve.

I would be happy with unsafeFromJust to not be misleading.

evilsoft commented 4 years ago

I think we should shy away from these two functions.

The use of is[instanceType] for Sum (Union) Types (Maybe, Either, Result, etc) nudges the user to a more imperative style. IMO the value I get out of ADTs, is the ability to remove a majority of the boiler plate that comes with imperative code and just use the types to dictate flow and logic. Providing those is[instanceType] functions in the lib is not really a "general" thing and really should be provided by the end user if they need them. As @dalefrancis88 has shown with his suggestion, we provide all the parts/bits/pieces you can use to craft the the functions you need.

As far as the fromJust, there is no way to generally go from Maybe a to any other type and we want to shy away from throwing anything but TypeErrors in this lib. (Those are the only errors we throw). So that is not feasible as there is no general case. We do provide an assortment of Natural Transformations for the Sum Types from one Sum Type to another Sum Type. But for moving from a SumType to another non-Sum type, that would be a one-off thing. To account for that generally, we would have to write a function for EVERY type the lib provides and force a user into whatever we decide to do when there is "structure" mismatch. So it makes more sense to NOT implement this and let the user write their own helpers specific to their context.

It really all comes down to this being a REALLY large library as is. so we want to keep to functions/patterns that provide the general case to the user and let the user write the helpers that they need for their specific context.

JonathanILevi commented 4 years ago

I guess that is reasonable. They are not that hard to implement oneself.

For anyone who sees this looking for an implementation here is one:

const isJust =
    either(constant(false), constant(true))
const fromJust =
    either(Identity, ()=>{throw Error();})