我有以下数据:
data LinkedList a = Node a (LinkedList a) | Empty deriving (Show)
而且我想知道如何在没有模式匹配的情况下从中获取单个值。
所以在基于C的语言中:list.value
我有以下数据:
data LinkedList a = Node a (LinkedList a) | Empty deriving (Show)
而且我想知道如何在没有模式匹配的情况下从中获取单个值。
所以在基于C的语言中:list.value
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
让我们使用 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)))
我不会为您的问题提供 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 语言做事的方式。
通常,您不需要提取所有值。如果你真的想提取,使用Comonad
extract
函数:
class Functor w => Comonad w where
extract :: w a -> a
...
通常Foldable
, Traversable
, Monoids
,Monad
更有Zippers
用
为了完整起见,让我提到一些我听说过“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 的参数($)
。
当然,可能不会在实际代码中使用它,因为它会引起其他人的混淆以及组合运算符的丢失。
我只是模式匹配。
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.