Closed zfoxdev closed 7 years ago
That sounds correct to me. You want a filter-style function.
Hello, @zfoxdev. Welcome to Sanctuary!
While I am encountering the following functionality:
Maybe#map :: Maybe a ~> (a -> b) -> Just b
The first thing to recognize is that Maybe
is a unary type constructor. Its type is as follows:
Maybe :: Type -> Type
So Maybe
is not a type, but Maybe String
is a type, as is Maybe a
.
Just
, on the other hand, is not a type constructor but a data constructor. It's a function for creating values of type Maybe a
. Its type is as follows:
Just :: a -> Maybe a
Let's consider the types involved in S.toMaybe('hello').map(str => undefined)
:
toMaybe :: a? -> Maybe a
toMaybe("hello") :: Maybe String
toMaybe("hello").map :: Maybe String ~> (String -> b) -> Maybe b
toMaybe("hello").map(str => undefined) :: Maybe Undefined
The type of the expression is Maybe Undefined
. This type has two members: Nothing()
and Just(undefined)
. It's not a particularly useful type, as we have Boolean
if we need a type with two members.
My suggestion is to replace or wrap str => undefined
. I assume it represents a third-party function (since a Sanctuary user would not deal with null
or undefined
by choice). This being the case, I suggest defining a wrapper function of type String -> Maybe Foo
rather than String -> Foo?
(the ?
indicates the possibility of undefined
). You could then use chain
rather than map
.
The fromMaybe
type error is to be expected. Sanctuary is preventing you from using an expression which can evaluate to values of different types. The problem lies with Just(undefined)
rather than with fromMaybe
. Avoid getting into that position and the fromMaybe
issue will resolve itself.
Let me know if this is helpful. I'm happy to clarify anything I have not done a good job of explaining. :)
Behaviour of map is correct. I think you want to do something like this you can do this:
// return Nothing instead of undefined
m.chain(str => Nothing)// Nothing
or
// return undefined
const f = str => undefined
// but use toMaybe to convert to maybe
m.chain(a => S.toMaybe(f(a))) //Nothing
What's happening in your case is that, you have value of type Maybe String
and it's map
takes function from String
to b
and returns Maybe b
:: Maybe String -> (String -> b) -> Maybe b
b
for you is substituted by Nullable a
or ?a
which means that it is either null/undefined
or some value a
, and result is Maybe (Nullable a)
:: Maybe String -> (String -> Nullable a) -> Maybe (Nullable a)
so Just(undefined) is valid value.
@davidchambers I was writing answer at the same time :d
I was writing answer at the same time
No problem. Two explanations are better than one. :)
@davidchambers I did end up using a chain as a work around. Like this example mock code:
let str = S.toMaybe('hello').chain(str => S.toMaybe(undefined))
let result = S.fromMaybe('world', str) //result is set to 'world' as desired
My understanding however based on the documentation is that this should be done automatically when using .map()
.
I understand that the real issue lies with the Just(undefined)
and more specifically with the following issue:
Just(undefined) :: Just(undefined)
Maybe(undefined) :: Nothing
What I would expect from the .map()
operation is the second case. Which would no longer make the S.fromMaybe()
statement an issue.
Here is a more detailed example to show the desired usage:
let obj = {
foo: 'bar'
};
//This works
let str = S.toMaybe(obj).map(o => o.foo) // :: Just('bar')
let result = S.fromMaybe('bam', str) // result is correctly set to 'bar'
//This does not
let str2 = S.toMaybe(obj).map(o => o.biz) // :: Just(undefined) when Maybe(undefined) aka Nothing() is desired
let result2 = S.fromMaybe('bam', str2) // results in type error rather than a default of 'bam'
It's not map
's responsibility to handle this case, all it does is run a function, any function, on the contents of the container it's applied to. If there is a chance of failure then you need to use a function that can handle that (such as S.get
in this case) and use chain
instead.
If you are not sure about biz being present in the object or not then you shouldn't use map and instead have function: safeGetBiz = (o) => o.biz == null ? Nothing : Just(o.biz)
and use it with chain:
let str = S.toMaybe(obj).map(o => o.foo) // Just('bar')
let result = S.fromMaybe('bam', str) // result is correctly set to 'bar'
// This does as well
let str2 = S.toMaybe(obj).chain(safeGetBiz) // Nothing
let result2 = S.fromMaybe('bam', str2) // results is 'bam'
You can generalize safeGetBiz
to
safeGet = (key) => (o) => o[key] == null ? Nothing : Just(o[key])
safeGetBiz = safeGet('biz')
let str2 = S.toMaybe(obj).chain(safeGetBiz)
Sanctuary already has something like safeGet
:
get :: Accessible a => TypeRep b -> String -> a -> Maybe b
Stefano and Irakli have addressed the crux of your recent comment, @zfoxdev, but I would like to clarify the intended meaning of ::
in the Sanctuary documentation.
I understand that the real issue lies with the
Just(undefined)
and more specifically with the following issue:Just(undefined) :: Just(undefined) Maybe(undefined) :: Nothing
It looks as though you're using ::
to mean evaluates to, as in 2 + 2
evaluates to 4
, but in Hindley–Milner notation ::
has a different meaning.
::
means is a member of. We can write ['foo', 'bar', 'baz'] :: Array String
as shorthand for ['foo', 'bar', 'baz']
is a member of Array String
. This implies that the text to the left of the ::
must be a JavaScript expression, and the text to the right of the ::
must be a type signature. Put tersely, <value> :: <type>
.
Just(undefined) :: Just(undefined)
On the left of the ::
you have Just(undefined)
, a valid expression. Just(undefined)
, though, is not a type, so cannot appear on the right of the ::
. The correct type is Maybe Undefined
. This makes sense when one considers the type of Just
:
Just :: a -> Maybe a
Just
takes a value of some type a
and returns a value of type Maybe a
. When Just
is applied to undefined :: Undefined
we replace all occurrences of a
in the type signature with Undefined
giving Just :: Undefined -> Maybe Undefined
.
Maybe(undefined) :: Nothing
I think you mean toMaybe(undefined)
, which evaluates to Nothing()
. The type of Nothing()
, though, is Maybe a
, so one could write:
toMaybe(undefined) :: Maybe a
Maybe.map is documented as follows:
Maybe#map :: Maybe a ~> (a -> b) -> Maybe b
While I am encountering the following functionality:
Maybe#map :: Maybe a ~> (a -> b) -> Just b
For example the following code:
S.toMaybe('hello').map(str => undefined)
Will result in:Just(undefined)
When the following is desired:Nothing
This leads to the following issue with the code bellow: