Appendix III: Fission Style Guide
We focus on storytelling in our code
Type signatures are required
Right
Wrong
id :: a -> a
id x = x
id
:: a
-> a
id x = x
Right
Wrong
rawList ::
( MonadRIO cfg m
, Has IPFS.BinPath cfg
, Has IPFS.Timeout cfg
, HasProcessContext cfg
, HasLogFunc cfg
)
=> m (ExitCode, Lazy.ByteString, Lazy.ByteString)
rawList = IPFSProc.run' ["bootstrap", "list"]
rawList :: MonadRIO cfg m
=> Has IPFS.BinPath cfg
=> Has IPFS.Timeout cfg
=> HasProcessContext cfg
=> HasLogFunc cfg
=> m (ExitCode, Lazy.ByteString, Lazy.ByteString)
rawList = IPFSProc.run' ["bootstrap", "list"]
Why is it like this? A major reason is that the syntax highlighter in VS Code breaks with the
::
on the next line. We've also reverted to using tuple-style constraints, because in practive they making distinguishing between the constraints and argument types.Indent multi-line pipelines by 2 from the first item. This makes it consistent in
do
-notation and normal notations.Right
Wrong
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
As much as possible, pipe in one direction
Right
Wrong
Hku.Password <| encodeUtf8 <| Hku.password <| Hku.api <| manifest
Hku.Password <| encodeUtf8 (manifest |> Hku.api |> Hku.password)
Break long pipelines into multiple lines where possible
Right
Wrong
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
app |> condDebug |> CORS.middleware |> runner settings |> liftIO
Do not mix pipes with the bind operator
Right
Wrong
app <- Web.app
app
|> condDebug
|> CORS.middleware
|> runner settings
|> liftIO
Web.app
>>= condDebug
|> CORS.middleware
|> runner settings
|> liftIO
Refrain from defining functions in point-free style:
Right
Wrong
docs :: Web.Host -> Swagger
docs host' =
host'
|> app (Proxy @Web.API)
|> dns
|> ipfs
|> heroku
|> ping
|> user
docs :: Web.Host -> Swagger
docs = app (Proxy @Web.API)
. dns
. ipfs
. heroku
. ping
. user
However, they often do read well in a pipeline:
Right
Acceptable
alphaNum :: MonadIO m => Natural -> m Text
alphaNum len =
len
|> bsRandomLength
|> fmap (decodeUtf8Lenient . BS.filter isAsciiAlphaNum)
alphaNum :: MonadIO m => Natural -> m Text
alphaNum len =
len
|> bsRandomLength
|> fmap (\str ->
str
|> BS.filter isAsciiAlphaNum
|> decodeUtf8Lenient)
Whenever possible, generalize
IO
functions to MonadIO
. This prevents you from needing to generalize the function at the call site, while still able to use it in IO
contexts.Right
Wrong
fromHandler :: MonadIO m => Handler a -> m a
fromHandler handler =
liftIO <| runHandler handler >>= \case
Right inner -> pure inner
Left servantErr -> throwM servantErr
-- In use...
server appHost = Web.Swagger.server fromHandler appHost
:<|> IPFS.server
:<|> const Heroku.server
:<|> User.server
:<|> pure Ping.pong
:<|> DNS.server
fromHandler :: Handler a -> IO a
fromHandler handler =
runHandler handler >>= \case
Right inner -> pure inner
Left servantErr -> throwM servantErr
-- In use...
server appHost = Web.Swagger.server (liftIO . fromHandler) appHost
:<|> IPFS.server
:<|> const Heroku.server
:<|> User.server
:<|> pure Ping.pong
:<|> DNS.server
Last modified 3yr ago