Confused with FS.Aff

aff
#1

Hello,
Can somebody explain me why in the below code I need to use filterA in order to filter the Array that readdir returns? In theory the signature for readdir is
readdir :: FilePath -> Aff (Array FilePath)
So once you unwrap the AFF to files, why can’t I just make a normal filer? Is it because the filter function wraps everything on an applicative? According to type inference pure returns an Applicative, so using filterA seems to make sense, but I don’t understand why pure is needed here or why does it returns an applicative instead of something else. It is too smart for me…

import Data.Array (filterA)
import Data.Maybe (maybe)
import Data.String.CodeUnits (charAt, singleton)
import Effect (Effect)
import Effect.Aff (launchAff_)
import Effect.Class (liftEffect)
import Effect.Console (log)
import Node.FS.Aff (stat, readdir)
import Node.FS.Stats (isDirectory)

main :: Effect Unit
main = launchAff_ do
  files <- readdir "."
  files' <- flip filterA files \file -> do
    stat <- stat file
    pure $ isDirectory stat
      && (maybe false (singleton >>> (_ /= ".")) $ charAt 0 file)
  liftEffect $ log $ show files'

On a side note, why is launchAff now returning a fiber so we need to use lauchAff_ instead? And, why don’t change the code to use fibers instead of just use launchAff_?
Thanks in advance.

#2

So once you unwrap the AFF to files

readdir works like bash’s ls: it prints the file paths of both files and directories, but not their contents.

Once you get an array of file paths, you can then use the NodeJS bindings to determine whether or not the given path is a file or a directory. The filterA means “apply this function to each file path in the array. The output of that function, a boolean, will determine whether to include that file path or not.” The stat function provides additional statistics on that file path, one of which is whether it’s a directory, a file, a symbolic link, or something else.

According to type inference pure returns an Applicative, so using filterA seems to make sense

Not quite. There is no such thing as “an Applicative.” Rather, there are data types that have instances for the Applicative type class. If a data type (e.g. Maybe) has an instance for Applicative and Monad, using pure does not return an Applicative, but a data type’s value that has an Applicative instance (e.g. a Maybe value, such as Nothing or Just value). For some values, this isn’t a big deal (e.g. to get a Maybe Int, I could write Just 4 instead of pure 4). For other values, this isn’t as straightforward and pure is the only way to do it easily/well (e.g. Aff).

I don’t understand why pure is needed here or why does it returns an applicative instead of something else.

pure can put a value in a data type that has an Applicative or Monad instance (since an instance for Monad requires an instance for Applicative). It’s best to think of it as "if I don’t know what data type I’m actually working on (e.g. Maybe, List, Either, etc.), I know that I can use pure to put a value into that data type if that data type has an instance for Applicative.
In your example, it’s because filterA requres the \file -> ... function to return a Monadic type (which I believe is Aff Boolean in this case)

On a side note, why is launchAff now returning a fiber so we need to use lauchAff_ instead? And, why don’t change the code to use fibers instead of just use launchAff_?

launchAff provides a fiber in case you want to kill it later (e.g. concurrent but not parallel code). launchAff_ is a shortcut for map (\output -> unit) affComputation. In other words, ignore the output of the computation and just run the computation.

If you need a better understanding of the Monad type class hierarchy and how Aff works, I’d recommend checking out my learning repo:

#3

You need filterA here rather than the normal filter because you are wanting to perform more effects in order to decide whether to keep the file or not; using stat to check whether something is a directory is effectful.

1 Like
#4

Yes, that is the reason, thanks. I was guessing that, but I was not understanding how I was able to apply filterA to an Aff monad, because filterA wants a function that returns an applicative and Aff is just a monad. Thanks to the explanation of @JordanMartinez now I understand that a monad is also an applicative.

Thanks

#5

Wow @JordanMartinez, what an impressive answer, thank you very much.
I have familiarity with functional programming on untyped languages (clojure, lua, javascript, some python), but typed ML style languages are a totally different beast.
I’m familiar with all the most basic idioms of FP, so filter (or even filterA) is not new to me, however category theory is something I’m not feel comfortable with yet. Sure I understand functors and their laws, but monads are a totally different beast. And the fact that Purescript changes radically every time I try it (I tried 0.11, then 0.12) does not help either. Hopefully this time will be the one…

Your explanation was very clarifying, and thanks for the link to your repository, it will be very handy for sure.

#6

Ok, I think I’m gaining a bit more of understanding about how all this works by refactoring the code to make it less concise but more readable to me.
This is my own version of the code

isHidden :: Char -> Boolean
isHidden = singleton >>> (_ /= ".")

isVisibleDir :: String -> Aff Boolean
isVisibleDir file = do
  stat <- stat file
  pure $ isDirectory stat && (maybe false (isHidden) $ charAt 0 file)

main :: Effect Unit
main = launchAff_ do
  files <- readdir "."
  files' <- filterA isVisibleDir files
  liftEffect $ log $ show files'

By dividing the code into smaller chunks and giving them a signature I now understand better how this all works.
Thanks to all.

2 Likes
#7

Glad you’re figuring this stuff out!

Monads are just an abstract way of modeling sequential computation. I’m not sure how far you’ve read into my repo, but seeing some OO examples in comparison to their FP counterparts can help bridge the gap, too:

  • There are the “pure/non-effectful” monads:
    • Useful Monads.md shows what Maybe monad, Either monad, and List monad’s corresponding JavaScript is.
  • There are the “impure/effectful” monads (i.e. monad transformers):
    • MaybeT is the Maybe monad
    • ExceptT is the Either monad
    • ListT works the same as the above if you get how the List monad works.
    • Foundations is a folder explaining the Function monad, which is a core idea behind for monad transformers.
    • Implementing a Monad Transformer is a folder that explains (in a slightly different way than the Foundations folder) how we get MonadState and StateT.
#8

Luckily for me I spent 99.9% of my dev life between mostly functional and imperative/procedural codebases. I really hate OO and I used it in very rare situations.
Also I’m already familiar with some basic monads like maybe and Either, and I even experienced the monad hell :rofl:
That said, I still have a lot to learn, and your repository is a very pragmatic and useful resource. Thank you for the links!

1 Like