2

我有以下数据:

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

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

4

6 回答 6

6

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
于 2013-08-14T03:29:10.347 回答
3

让我们使用 monad 来做你想做的事。Monad 很棒,因为在定义它们时,您可以重新定义对您的意义;=意义。(这是 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.

更复杂

不过,Monad 对其他事情也有好处,有时它是表达某事的最佳方式:

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

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

ghci> combinationsWith (+) (Node 3 (Node 4 Empty)) (Node 10 (Node 100 Empty))
Node 13 (Node 103 (Node 14 (Node 104 Empty)))
于 2013-08-13T22:40:23.680 回答
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 中的结构成员相比,它有一些权衡:投影是一流的,因此可以用作参数和返回值;但它们与所有其他函数位于相同的命名空间中,这可能会导致不幸的名称冲突。

因此,在直接数据类型(即非递归)的情况下,访问成员的语法大致处于相同的便利级别:whole.part对于类 C 语言与part whole对于 Haskell。

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

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

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

于 2013-08-14T05:55:08.227 回答
1

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

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

通常Foldable, Traversable, Monoids,Monad更有Zippers

于 2013-08-14T20:31:45.620 回答
1

为了完整起见,让我提到一些我听说过“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 中意味着翻转应用程序 operator 的参数($)

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

于 2013-08-26T09:12:53.337 回答
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.
于 2013-08-14T02:07:03.553 回答