0

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.

the-konapie
  • 601
  • 3
  • 10
  • The `null` check *may* be done using something like `Foldable`'s [`null`](https://hackage.haskell.org/package/base-4.8.1.0/docs/Data-Foldable.html#v:null) method. But then the problem is how to return the correct value in the empty case... – Bakuriu Nov 14 '15 at 22:01
  • `Text` is not a `Foldable` - as it is no container how would you write `foldMap`, except for `fold = foldl` and `foldMap = undefined`, which is a bad idea – epsilonhalbe Nov 14 '15 at 22:15
  • 1
    @epsilonhalbe, it's true that `Text` cannot be `Foldable`, but not for the reasons you give. The real problem is that only types of kind `* -> *` can be `Foldable`, and `Text` has kind `*`. – dfeuer Nov 14 '15 at 22:27
  • @epsilonhalb, consider `data T :: * -> * where T :: Text -> T Char`. You can write a perfectly legitimate and sensible `Foldable` instance for `T`. – dfeuer Nov 14 '15 at 22:29
  • the-konapie, why do you want such a function? It seems rather strange. – dfeuer Nov 14 '15 at 22:33
  • @dfeur, I want to use these functions to generate text that will be shown to the user of my game. I have use cases for both functions and was wondering if I could somehow write a single function that can be used in both cases. – the-konapie Nov 14 '15 at 23:09
  • @dfeuer that was what I was trying to say with container, I am still not precise enough, sometimes - but the kind `* -> * was what I had in mind – epsilonhalbe Nov 14 '15 at 23:09

1 Answers1

2

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
epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74