I am attempting to learn purescript
.
I have a button in some HTML that I am trying to print the class name of. I am building and browserifying
using pulp
.
The function I am using is querySelector:
import Web.DOM.ParentNode (querySelector)
This returns the item I want, Element, within two "boxes": an outer Effect monad and an embedded Maybe monad:
> :type querySelector
QuerySelector -> ParentNode -> Effect (Maybe Element)
My Effect monad looks like:
getBtn :: Effect Unit
getBtn = do
doc <- map toParentNode (window >>= document)
button <- querySelector (wrap "#btn") doc
... Need to extract class name from 'button' here
I know I can get to the inner Maybe by calling bind (>>=) on the outer Effect monad. My first plan of attack was to unbox the Maybe manually using the maybe funtion. Here's how you do this for a simple Int:
> maybe 0 (\x -> x) $ Just 7
7
for a Maybe Element I think it would look something like:
unboxMaybeElement :: Maybe Element -> Element
unboxMaybeElement Nothing = Nothing
unboxMaybeElement (Just a) = maybe (Element ..note: trying to create a default element here) (\x -> x) a
One problem is I cannot find a constructor for type Element, so I am unable to supply a default value (the first arg of maybe), so I can't use this method.
In addition, I can't find a definition for the Element type.
I also read that it's not a good idea to reach inside a Maybe anyway, but instead to lift the functions going against it by using a functor (like map or fmap
).
In this vein I tried to call an external function like:
button <- querySelector (wrap "#btn") doc
let cn = getClassName button
log $ "classname=" <> show cn
-- The basic question is how to implement this function?
getClassName :: Maybe HTMLElement -> String
getClassName e = map className (map fromElement e)
Note: className and fromElement are supplied in Web.HTML.HTMLElement.
As you can see, I'm trying to promote up the functions by calling them with a map, because the functions do not deal with Maybes, but I'm getting stuck in a morass of type conflicts between "Maybe Element" and "Element" and "HTMLElement" vs "Element" etc.
Yes, I admit I have a couple of issues going on here, and this isn't really a beginners problem, but it just irks me that something so simple as obtaining the class name of an HTML object is so hard. Surely, I must be missing something?
BTW, Here is my html:
<!doctype html>
<html>
<body>
<script src="index.js"></script>
<p>
<input type="button" class="btn-class" value="click me" id="btn"/>
</p>
</body>
</html>
The more general question is: how do you get to values nested two monad levels deep?
Actually you did a good job by investigating this problem on your own, I can just suggest refactoring based on the example, that you've provided.
Let's say we don't care about logging failed messages on a particular case. That means, that we can replace every such a logging by pure unit
:
getBtn :: Effect Unit
getBtn = do
log "now in getBtn"
doc <- map toParentNode (window >>= document)
mbtn <- querySelector (wrap "#btn") doc
case mbtn of
Nothing -> pure unit
Just btn -> do
let mhtmlEl = fromElement btn
case mhtmlEl of
Nothing -> pure unit
Just htmlEl -> do
let cn = className htmlEl
log $ "classname below:"
cn >>= log
Then we can notice an interesting pattern here:
case mbtn of
Nothing -> pure unit
Just btn -> do
let mhtmlEl = fromElement btn
i.e we want to apply fromElement
function on a btn
value if it Just btn
or do nothing if it's Nothing
. The resulting type must be Maybe
value.
The first thought is using map
function, but the type of fromElement
is fromElement :: Element -> Maybe HTMLElement
, so the resulting type will be Maybe (Maybe a))
.
Anyway, we can even search such a function by type and the first result is bind
(the same as (>>=)
). So let's refactor (with pointing the types as comments just for clarity, but sure it's not necessary):
getBtn :: Effect Unit
getBtn = do
log "now in getBtn"
doc <- map toParentNode (window >>= document)
mbtn <- querySelector (wrap "#btn") doc
let mhtmlEl = fromElement =<< mbtn -- Maybe HTMLElement
case mhtmlEl of
Nothing -> pure unit
Just htmlEl -> do
let cn = className htmlEl
log $ "classname below:"
cn >>= log
The next step is to reduce another case
expression. Here's using map (the same as <$>
) will be enough:
getBtn :: Effect Unit
getBtn = do
log "now in getBtn"
doc <- map toParentNode (window >>= document)
mbtn <- querySelector (wrap "#btn") doc
let mhtmlEl = (fromElement =<< mbtn) -- Maybe HTMLElement
let mCn = className <$> mhtmlEl -- Maybe (Effect String)
case mCn of
Nothing -> log "no such element"
Just cn -> do
log $ "classname below:"
cn >>= log
The resulting type of getBtn
function, must be Effect Unit
, so every case of Maybe
value must be processed here. I would leave it like this, since it quite clear, what's going on here. But if you seeking a concise representation, then maybe
function can be applied here:
getBtn :: Effect Unit
getBtn = do
log "now in getBtn"
doc <- map toParentNode (window >>= document)
mbtn <- querySelector (wrap "#btn") doc
let mhtmlEl = (fromElement =<< mbtn) -- Maybe HTMLElement
let mCn = className <$> mhtmlEl -- Maybe (Effect String)
maybe (log "no such element") (\cn -> log "classname below:" *> cn >>= log) mCn
Based on some similar code I saw in this video, I was able to get something that compiled. I'm not saying this is the best way, or even a good way, just that it works:
getBtn :: Effect Unit
getBtn = do
log "now in getBtn"
doc <- map toParentNode (window >>= document)
mbtn <- querySelector (wrap "#btn") doc
case mbtn of
Nothing -> log "mbtn failed"
Just btn -> do
let mhtmlEl = fromElement btn
case mhtmlEl of
Nothing -> log "mhtml failed"
Just htmlEl -> do
let cn = className htmlEl
log $ "classname below:"
cn >>= log
pure unit
pure unit
pure unit
pure unit
The "m" on some variable names ("mbtn", "mhtmlEl") is presumably to denote it's a wrapped object i.e. monadic. It's important that in your case Just
statements you specify a different variable than in the case of
statement, one without an "m" in front ("btn", "htmlEl"), to denote it's a raw or unwrapped value.
the pure unit
lines are essentially dummy lines to appease the requirement that a monad always returns something other than a "binder" (a let or '<-' assignment), so you may or may not need them depending on how your do workflow is constructed.
There are effectively two levels of Maybe
in this example: one on the querySelector
result and one on the fromElement
result. Add in another do
to process the raw HTMLElement
and you're up to three levels of do
.
Unfortunately, the querySelector
appears to be returning Nothing
for me, but this is an orthogonal issue. That was the whole motivation for printing the className -- to determine if it was finding the element or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With