如何从Haskell中的数据中获取值

时间:2013-08-13 22:02:36

标签: list haskell functional-programming

我有以下数据:

data LinkedList a = Node a (LinkedList a) | Empty deriving (Show)

我想知道如何在没有模式匹配的情况下从中获取单个值 所以使用基于 C 的语言:list.value

6 个答案:

答案 0 :(得分:4)

Jared Loomis,听起来你想要访问LinkedList的不同部分而不必编写自己的帮助函数。因此,从这个角度来看,有一种编写数据构造函数的替代技术,可以为您编写这些辅助函数。

data LinkedList a = Node { nodeHead :: a, rest :: LinkedList a} | Empty
  deriving (Show)

示例用法:

*Main> let example = Node 1 (Node 2 (Node 3 Empty))
*Main> example
Node {nodeHead = 1, rest = Node {nodeHead = 2, rest = Node {nodeHead = 3, rest = Empty}}}
*Main> nodeHead example
1
*Main> nodeHead . rest $ example
2
*Main> nodeHead . rest . rest $ example
3

小心虽然nodeHead和rest被认为是部分函数,​​但在Empty上使用时抛出异常:

*Main> nodeHead Empty
*** Exception: No match in record selector nodeHead
*Main> rest Empty
*** Exception: No match in record selector rest

如果你想要一些有修复后语法的东西我会推荐 lens包。

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data LinkedList' a = Node' { _nodeHead' :: a, _rest' :: LinkedList' a} | Empty'
  deriving (Show)
makeLenses ''LinkedList'

*Main> example ^? rest' 
Just (Node' {_nodeHead' = 2, _rest' = Node' {_nodeHead' = 3, _rest' = Empty'}})
*Main> example ^? rest' . nodeHead'
Just 2

答案 1 :(得分:3)

让我们用monad来做你想做的事。 Monads很棒,因为在定义它们时,您可以重新定义;=对您的意义。 (这是Haskell,我们使用换行符和缩进来指示;的去向和<-以区别于永久定义=。)

我将不得不使用模式匹配来制作实例,因为我还没有别的东西可以继续:

instance Monad LinkedList where
   Empty >>= f = Empty
   (Node a as) >>= f = f a `andthen` (as >>= f)
   return a = Node a Empty

绑定运算符>>=<-运算符后面的可配置管道。在这里,我们选择了;来表示下一个元素,在工作中使用辅助函数addthen

andthen :: LinkedList a -> LinkedList a -> LinkedList a
Empty `andthen` list = list
(Node a list) `andthen` therest = Node a (list `andthen` therest)

现在我们可以使用monad表示法一次获取一个值。例如,让我们将函数应用于链表中的元素:

applyToElements :: (a -> b) -> LinkedList a -> LinkedList b
applyToElements f list = do
   val <- list
   return (f val)
ghci> applyToElements ( ++ ", yeah" )  (Node "Hello" (Node "there" Empty))
Node "Hello, yeah" (Node "there, yeah" Empty)

更简单的方法

我根本不会那样定义。我直接使用了模式匹配:

applyToElements :: (a -> b) -> LinkedList a -> LinkedList b
applyToElements f Empty = Empty
applyToElements f (Node a list) = Node (f a) (applyToElements f list) 

然后宣布

instance Functor LinkedList where
   fmap = applyToElements

因为按元素应用其他函数的函数的通常名称是fmap

更复杂

Monads可能对其他东西有好处,有时它是表达某些东西的最佳方式:

combinationsWith :: (a -> b -> c) -> LinkedList a -> LinkedList b -> LinkedList c
combinationsWith f list otherlist = do  -- do automatically traverses the structure 
    val <- list                    -- works like   val = list.value 
    otherval <- otherlist          --              otherval = otherlist.value
    return (f val otherval)        -- once for each value/othervalue

因为我们在为LinkedList定义andthen时选择使用<-,如果我们使用两个列表,它将使用第一个列表然后第二个以嵌套子环路的方式使用,因此otherval值的变化频率高于第一个val,因此我们得到:

ghci> combinationsWith (+) (Node 3 (Node 4 Empty)) (Node 10 (Node 100 Empty))
Node 13 (Node 103 (Node 14 (Node 104 Empty)))

答案 2 :(得分:2)

我不会在你的问题中提出一个Haskell解决方案,而是提出一个与C更现实的比较,并建议你

struct list {
    int value;
    struct list *next;
};

int main(void) {
    struct list *list = NULL;
    int val;

    /* Goodbye, cruel world! */
    val = list->value;

    /* If I had "pattern-matched"... */
    if (list == NULL) {
        val = 0;
    } else {
        val = list->value;
    }
    return 0;
}

如果不检查C中的NULL情况(对应于Haskell中的模式匹配),则在执行程序的某个时刻会发生SEGFAULT崩溃,而不是出现编译错误。

换句话说,如果不在C中进行案例分析,就无法从“可能为空”的递归数据类型中获取值!如果你重视程序的稳定性,至少不会这样。 Haskell不仅坚持你做正确的事情,而且提供方便的语法来帮助你这样做!

正如其他答案中所提到的,记录定义语法为您提供了方便的投影(即访问者函数),与在C中访问struct成员相比,它们有一些权衡:投影是一流的,因此可以用作参数和返回值;但它们与所有其他功能位于同一名称空间中,这可能导致不幸的名称冲突。

因此,在直接数据类型(即非递归)的情况下,访问成员的语法大致处于同样的便利水平:{C}类语言whole.partpart whole for {{1}} Haskell中。

对于递归类型(如示例中的那个),其中一个或多个成员引用相同类型的可能为空的实例,在提取值之前,在任一语言中都需要进行大小写分析。在这里,您需要使用案例分析或可能的异常处理程序将您的C语言字段访问包装起来。在Haskell中,你有各种形式的语法糖用于模式匹配,往往更简洁。

此外,请参阅有关Monads的答案,了解如何为Haskell中的“可能为空”类型提供更多更多方便性,隐藏大部分用于库函数内多步计算的中间模式匹配

总结一下:我的观点是,当你花时间学习Haskell的模式和习语时,你可能会发现自己错过了用C语言做事的方式越来越少。

答案 3 :(得分:1)

通常,您不需要提取所有值。 如果您真的想要提取,请使用Comonad extract函数:

class Functor w => Comonad w where
    extract :: w a -> a
    ...

通常FoldableTraversableMonoidsMonadZippers更有用

答案 4 :(得分:0)

我只是模式匹配。

llHead :: LinkedList a -> a
llHead Empty      = error "kaboom"
llHead (Node x _) = x

如果你想要一个特定索引的元素,尝试这样的东西(也使用模式匹配):

llIdx :: LinkedList a -> Int -> a
llIdx l i = go l i
  where go Empty       _ = error "out of bounds"
        go (Node x _)  0 = x
        go (Node _ xs) j = go xs (j - 1)

确保这有效:

import Test.QuickCheck

fromList []     = Empty
fromList (x:xs) = Node x (fromList xs)

allIsGood xs i  = llIdx (fromList xs) i == xs !! i

llIdxWorksLikeItShould (NonEmpty xs) =
  let reasonableIndices = choose (0, length xs - 1) :: Gen Int
  in  forAll reasonableIndices (allIsGood xs)

-- > quickCheck llIdxWorksLikeItShould
-- +++ OK, passed 100 tests.

答案 5 :(得分:0)

为了完整起见,让我提一下我听过“dot hack”这个名字的内容:

Prelude> data LinkedList a = Node { nodeHead :: a, nodeRest :: LinkedList a} | Empty deriving (Show)
Prelude> let example = Node 1 (Node 2 (Node 3 Empty)) :: LinkedList Int
Prelude> let (.) = flip ($)
Prelude> example.nodeRest.nodeHead
2

它只是意识到C样式访问.与将访问器函数应用于之前提到的对象相同,在Haskell中意味着转换应用程序运算符($)的参数。

当然,人们可能不会在实际代码中使用它,因为它会导致其他人混淆和组合运算符的丢失。