How to get to inner Maybe monad to extract class name from html button in purescript?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



How to get to inner Maybe monad to extract class name from html button in purescript?



I am attempting to learn purescript.


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.


browserifying


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).


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?





have you tried fmap, to promote a function? map operate on lists.
– Robert K
Aug 10 at 7:47


fmap


map





Yes, I was aware of this distinction and did try, but for the life of me I can't find any fmap definition in purescript? It's certainly not in the prelude, or Data.Functor, or Data.Maybe. I do see a fmap in ghci though. There are some differences between Haskell and Purescript, and maybe this is one?
– vt5491
Aug 10 at 7:58






Forgive me, i missed that this was purescrict. You can see here: github.com/purescript/purescript-prelude/blob/master/src/Data/… That the mapping operation is indeed called map in purescript. What is your type signature for className? and can you edit your post to include the error you get when you tried to lift the function?
– Robert K
Aug 10 at 8:17



map


className





No clear idea, but two thoughts: 1. getClassName :: Maybe HTMLElement -> String looks odd: what string should be returned in case of Nothing? 2. Not sure where fromElement is coming from, but I guess it returns Maybe as well? If so, mapping over a Maybe with this function would add another "layer" of Maybe. Sounds like you need >>= or do-notation. My general advice would be to add type signatures and use typed holes to see what types the compiler expects. Hope this helps.
– stholzm
Aug 10 at 8:29



getClassName :: Maybe HTMLElement -> String


Nothing


fromElement


Maybe


Maybe


Maybe


>>=




2 Answers
2



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:


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.


fromElement


btn


Just btn


Nothing


Maybe



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)).


map


fromElement


fromElement :: Element -> Maybe HTMLElement


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):


bind


(>>=)


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:


case


<$>


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


Maybe


maybe


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





Yes, that’s the kind of solution I was striving for and knew had to be there somewhere! It demonstrates to me how Haskell (PureScript) really is mathematical and functions really are expressions. I essentially had “y= x + x + x” and you showed it can be written more simply as “y = 3x”, for example. Interesting how Haskell allows for such objective and almost algorithmic refactorings. Thanks for the lesson in precision thinking.
– vt5491
Aug 11 at 15:40



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.


case Just


case of



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.


pure unit



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.


Maybe


querySelector


fromElement


do


HTMLElement


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.


querySelector


Nothing






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

make 2 or more post in bootsrap

Store custom data using WC_Cart add_to_cart() method in Woocommerce 3

Firebase Auth - with Email and Password - Check user already registered