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:
let str = S.toMaybe('hello').map(str => undefined)
let result = S.fromMaybe('world', str)
TypeError: Type-variable constraint violation
fromMaybe :: a -> Maybe a -> a
^ ^
1 2
1) "world" :: String
2) undefined :: Undefined
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鈥揗ilner 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
Most helpful comment
Hello, @zfoxdev. Welcome to Sanctuary!
The first thing to recognize is that
Maybeis a unary type constructor. Its type is as follows:So
Maybeis not a type, butMaybe Stringis a type, as isMaybe a.Just, on the other hand, is not a type constructor but a data constructor. It's a function for creating values of typeMaybe a. Its type is as follows:Let's consider the types involved in
S.toMaybe('hello').map(str => undefined):The type of the expression is
Maybe Undefined. This type has two members:Nothing()andJust(undefined). It's not a particularly useful type, as we haveBooleanif 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 withnullorundefinedby choice). This being the case, I suggest defining a wrapper function of typeString -> Maybe Foorather thanString -> Foo?(the?indicates the possibility ofundefined). You could then usechainrather thanmap.The
fromMaybetype error is to be expected. Sanctuary is preventing you from using an expression which can evaluate to values of different types. The problem lies withJust(undefined)rather than withfromMaybe. Avoid getting into that position and thefromMaybeissue 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. :)