我有以下数据:
data LinkedList a = Node a (LinkedList a) | Empty deriving (Show)
我想知道如何在没有模式匹配的情况下从中获取单个值
所以使用基于 C 的语言:list.value
答案 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.part
与part whole
for {{1}} Haskell中。
对于递归类型(如示例中的那个),其中一个或多个成员引用相同类型的可能为空的实例,在提取值之前,在任一语言中都需要进行大小写分析。在这里,您需要使用案例分析或可能的异常处理程序将您的C语言字段访问包装起来。在Haskell中,你有各种形式的语法糖用于模式匹配,往往更简洁。
此外,请参阅有关Monads的答案,了解如何为Haskell中的“可能为空”类型提供更多更多方便性,隐藏大部分用于库函数内多步计算的中间模式匹配
总结一下:我的观点是,当你花时间学习Haskell的模式和习语时,你可能会发现自己错过了用C语言做事的方式越来越少。
答案 3 :(得分:1)
通常,您不需要提取所有值。
如果您真的想要提取,请使用Comonad
extract
函数:
class Functor w => Comonad w where
extract :: w a -> a
...
通常Foldable
,Traversable
,Monoids
,Monad
,Zippers
更有用
答案 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中意味着转换应用程序运算符($)
的参数。
当然,人们可能不会在实际代码中使用它,因为它会导致其他人混淆和组合运算符的丢失。