Search code examples
haskelllifting

Haskell: writing a function such that it can be lifted to work on lists


I have the following 2 functions:

import qualified Data.Text as T

noneOnEmptyA :: T.Text -> T.Text
noneOnEmptyA txt | T.null txt = "None."
                 | otherwise  = txt


noneOnEmptyB :: [T.Text] -> [T.Text]
noneOnEmptyB txts | null txts = ["None."]
                  | otherwise = txts

Is it possible to write a single function that 1) accomplishes what noneOnEmptyA does, and 2) can be lifted such that it accomplishes what noneOnEmptyB does? The crux of the problem seems to be that noneOnEmptyA checks for empty text, while noneOnEmptyB checks for an empty list; however, noneOnEmptyA lifted so as to operate on lists (as in fmap noneOnEmptyA returning type [T.Text]) checks for empty text inside the list, rather than checking whether or not the list itself is empty.


Solution

  • one thing you could do is introduce a typeclass Nullable

    {-# LANGUAGE OverloadedStrings #-}
    module Stackoverflow where
    
    import           Data.Text (Text)
    import qualified Data.Text as T
    import           Data.Monoid
    import           Data.String
    
    class Nullable a where
        isNull :: a -> Bool
    
    instance Nullable Text where
        isNull = T.null
    
    instance IsString a => IsString [a] where
        fromString str = [fromString str]
    

    then you can write your function

    noneOnEmpty :: (Nullable a, IsString a, Monoid a) => a -> a
    noneOnEmpty a | isNull a = "None" <> mempty
                  | otherwise = a
    

    Update

    As @DanielWagner points out - the Monoid/mempty/<> part is not necessary

    noneOnEmpty :: (Nullable a, IsString a) => a -> a
    noneOnEmpty a | isNull a = "None"
                  | otherwise = a