转换为另一种数据类型

时间:2015-01-13 16:42:38

标签: haskell types

我有以下数据类型:

data Food = Butter Int | Apple Int Food | Meat Double Food deriving (Show)

data Basket = Basket { butter, apple :: Int, meat :: (Int,Double) } deriving (Show)

篮子包含黄油和苹果的数量,肉的数量和总重量(双倍)。

我试图写一个函数 food2basket :: Food - >购物篮,允许我将Food类型的值转换为Basket类型的值。

所以,如果我宣布这个食物的实例:

     andrew = Apple 1 ( Meat 0.60 ( Meat 0.70 ( Apple 1 ( Butter 1 ))))

通过应用函数我得到以下结果

 food2basket andrew == {butter = 1, apple= 2, meat = (2,1.30)}

到目前为止,我的代码如下:

emptyBasket = Basket {butter = 0, apple= 0, meat = (0,0)}
food2basket (Butter i)      = emptyBasket {butter = i}
food2basket (Apple i b)     = anotherBasket{apple = i + (apple anotherBasket)}
    where anotherBasket     = food2basket b
food2basket (Meat d b)      = auxFood (Meat d b) 0
    where
         auxFood(Meat d b) n= emptyBasket { meat = (n+1,d)} -- missing recursion

它正在向Apple" Apple"部分,甚至很难我发现很难在括号内使用递归。这就是为什么我使用了另一个篮子和where子句,这是非常令人困惑的。 我担心我无法用肉做同样的事情。 (它没有递归(还))

我希望通过模式匹配解决整个功能。

3 个答案:

答案 0 :(得分:3)

代码看起来有点混乱。您可能想要做的是定义一个将两个篮子组合在一起的函数:

addBaskets b1 b2 = Basket {butter = butter b1 + butter b2, apple = ...

然后你可以做类似

的事情
food2basket (Butter i)   = emptyBasket {butter = i}
food2basket (Apple  i b) = addBaskets (emptyBasket {apple = i}) (food2basket b)
food2basket (Meat   i b) = addBaskets (emptyBasket {meat  = i}) (food2basket b)

答案 1 :(得分:3)

扩展了MathematicalOrchid的答案,这种模式看起来很像Monoid

import Data.Monoid

instance Monoid Basket where
    mempty = Basket 0 0 (0, 0)  -- Same as emptyBasket
    mappend (Basket b1 a1 (m1, d1)) (Basket b2 a2 (m2, d2))
        = Basket (b1 + b2) (a1 + a2) (m1 + m2, d1 + d2)

如果您改为将Food类型写为

data FoodItem
    = Butter Int
    | Apple Int
    | Meat Double
    deriving (Eq, Show)

然后你可以

type Food = [FoodItem]

foodItemToBasket :: FoodItem -> Basket
foodItemToBasket (Butter i) = mempty { butter = i }
foodItemToBasket (Apple  i) = mempty { apple = i }
foodItemToBasket (Meat   d) = mempty { meat = (1, d) }

然后您只需使用map foodItemToBasketmconcat

foodToBasket :: Food -> Basket
foodToBasket = mconcat . map foodItemToBasket

现在将每个项目转换为一个篮子更简单,并且不包含递归,并且将这些篮子组合在一起的行为由mconcat处理,Data.Monoid由{{1}提供的更通用的函数}}

但是,如果你想要一个递归数据结构,你可以通过转向Free monad来实际上严重过度复杂化。我会详细说明细节,但它允许你这样做(你需要DeriveFunctor来编译它):

data FoodF f
    = Butter' Int
    | Apple' Int f
    | Meat' Double f
    deriving (Functor, Show)

type Food' = Free FoodF

butter' :: Int -> Food' ()
butter' i = liftF $ Butter' i

apple' :: Int -> Food' ()
apple' i = liftF $ Apple' i ()

meat' :: Double -> Food' ()
meat' d = liftF $ Meat' d ()

food'ToBasket :: Food' () -> Basket
food'ToBasket (Free (Butter' i)) = mempty { butter = i }
food'ToBasket (Free (Apple' i f)) = mempty { apple = i } <> food'ToBasket f
food'ToBasket (Free (Meat'  d f)) = mempty { meat = (1, d)} <> food'ToBasket f
food'ToBasket (Pure ()) = mempty

andrew :: Food' ()
andrew = do
    apple' 1
    meat' 0.6
    meat' 0.7
    apple' 1
    butter' 1

现在你可以做到

> food'ToBasket andrew
Basket { butter = 1, apple = 2, meat = (2, 1.2999999999998)}

(不精确的权重是由于IEEE浮点格式舍入错误,有关此信息散布在整个互联网上)

为什么你想要走这条路是愚蠢的,我只是觉得有趣的是你的Food的定义符合这个模式。它确实为您提供了一个免费的monad实例,它可以让您简单地列出一个人使用符号表示的不同项目,然后food'ToBasket处理&#34;解释&#34;这个monad结构变成了一个篮子。它确实意味着像

这样的东西
> food'ToBasket (butter' 1 >> apple' 1 >> meat' 0.5)
Basket { butter = 1, apple = 0, meat = (0, 0.0)}

因此,如果Butter'是结构中的最后一项,那么在遇到Butter'时,您基本上会遇到短路,而不是进行编译时检查,就像Nothing将会短路一样电路Maybe计算。

答案 2 :(得分:1)

这是一个更完整的解决方案:

addBaskets :: Basket -> Basket -> Basket
addBaskets b1 b2 =
    Basket (butter b1 + butter b2) (apple b1 + apple b2) ((m1 + m2), (w1 + w2))
    where (m1, w1) = meat b1
          (m2, w2) = meat b2

food2basket :: Food -> Basket
food2basket (Butter x)  = emptyBasket { butter = x }
food2basket (Apple x f) = addBaskets emptyBasket { apple = x } (food2basket f)
food2basket (Meat x f)  = addBaskets emptyBasket { meat = (1, x) } (food2basket f)