首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >怎样才能在不使用不安全函数的情况下,用Haskell为Foldable编写tailMay呢?

怎样才能在不使用不安全函数的情况下,用Haskell为Foldable编写tailMay呢?
EN

Stack Overflow用户
提问于 2019-11-21 22:38:39
回答 2查看 99关注 0票数 5

签名应为:

代码语言:javascript
复制
tailMay :: Foldable f => f a -> Maybe (f a)

我得到了headMay的这个定义,这很有意义,而且(对我来说)相当聪明:

代码语言:javascript
复制
headMay :: Foldable f => f a -> Maybe a
headMay = foldr (const . Just) Nothing

不幸的是,我还没能为tailMay想出任何类似的东西,而且similar implementations似乎使用了魔法或不安全的函数。

如果这是不可能的,那么对于我的目的来说,在(Applicative t, Foldable t)上工作的东西也是可以的(我猜这也意味着Traversable t)。

EN

回答 2

Stack Overflow用户

发布于 2019-11-21 23:35:06

Tail只对类似list的结构有意义,所以返回一个list是明智的,而不是返回相同的容器类型f。实现是微不足道的,可以用很多方法来完成,这里就是其中之一。

代码语言:javascript
复制
import Control.Monad (guard)
import Data.Foldable (toList)

tailMay :: Foldable f => f a -> Maybe [a]
tailMay xs = guard (not (null xs)) >> Just (drop 1 (toList xs))

但是,因为它适用于所有类似列表的结构,所以我们可以很酷地为所有具有IsList的东西定义它。

代码语言:javascript
复制
{-# LANGUAGE TypeFamilies #-}
import Control.Monad (guard)
import Data.Foldable (toList)
import GHC.Exts (IsList(Item, fromList))

tailMay :: (IsList l, Item l ~ a, Foldable f) => f a -> Maybe l
tailMay xs = guard (not (null xs)) >> Just (fromList (drop 1 (toList xs)))

编辑

上述解决方案的一个很好的好处是您可以将类型从参数更改为结果。例如,您可以从Vector开始,以Set作为结果。但这也有不利的一面,您需要自己指定结果类型:

因此,运行此命令将导致编译错误:

代码语言:javascript
复制
λ> tailMay [1 :: Int]
<interactive>:24:1-18: error:
    • Illegal equational constraint Item l ~ Int
      (Use GADTs or TypeFamilies to permit this)
    • When checking the inferred type
        it :: forall l. (IsList l, Item l ~ Int) => Maybe l
λ> tailMay [1 :: Int] :: Maybe [Int]
Just []

我们可以很容易地限制结果类型,如果上面对我们来说真的是一个问题或仅仅是一个不想要的效果:

代码语言:javascript
复制
tailMay :: (IsList l, Item l ~ a, Foldable f, l ~ f a) => f a -> Maybe l

在那之后,它工作得很好:

代码语言:javascript
复制
λ> import qualified Data.Vector as V
λ> tailMay $ V.enumFromTo 'a' 'd'
Just "bcd"
λ> tailMay [1 :: Int]
Just []
λ> tailMay V.empty
Nothing
λ> tailMay []
Nothing

警告-这一切都很棒,但不幸的是,不能保证fromList会有一个完整的实现:

代码语言:javascript
复制
λ> import qualified Data.List.NonEmpty as NE
λ> tailMay (5 NE.:| [] :: NE.NonEmpty Int)
Just *** Exception: NonEmpty.fromList: empty list

除了创建自己的类似IsList的类并为所有这样的数据结构提供自己的实例之外,没有通用的方法来保护自己不受它的影响。

票数 4
EN

Stack Overflow用户

发布于 2019-11-22 03:28:22

我不确定这个问题是否有一个正确的答案,尽管我想扩展@DanielWagner的评论,因为它可能是最好的选择,同时也是安全的:

代码语言:javascript
复制
-- | Law: (fromJust (length <$> tailMay l)) == (length tailMay) - 1,
--   if tailMay returns Just
class TailMay l where
  tailMay :: l a -> Maybe (l a)

instance TailMay NonEmpty where
  tailMay = LNE.nonEmpty . LNE.tail

instance TailMay [] where
  tailMay = liftMay tail null -- <- from 'safe' package

-- from 'safe' package
liftMay :: (a -> b) -> (a -> Bool) -> (a -> Maybe b)
liftMay func test val = if test val then Nothing else Just $ func val

也就是说,理想情况下,我也会更多地探索@dfeuer的评论。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58977458

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档