转换为另一种数据类型
Conversion to another Datatype
我有以下数据类型:
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 -> Basket 允许我将 Food 类型的值转换为 Basket 类型的值。
所以如果我声明这个 Food 实例:
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" 部分,即使很难我发现很难在括号内使用递归。这就是我使用 anotherBasket 和 where 子句的原因,这非常令人困惑。
正如我担心的那样,我无法对 Meat 做同样的事情。 (它还没有递归)
我想用模式匹配来解析整个函数。
代码看起来有点混乱。您可能想要做的是定义一个将两个篮子相加的函数:
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)
这里有一个更完整的解决方案:
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)
扩展 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 foodItemToBasket
和 mconcat
:
foodToBasket :: Food -> Basket
foodToBasket = mconcat . map foodItemToBasket
现在将每个项目转换为篮子变得更加简单并且不包含递归,并且将这些篮子组合在一起的行为由 mconcat
处理,这是 [=22 提供的更通用的函数=].
但是,如果您确实需要递归数据结构,您实际上可以通过转向 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 实例,让您可以简单地列出一个人使用 do 表示法拥有的不同项目,然后 food'ToBasket
将此 monad 结构处理 "interpreting" 到一个篮子中。它确实意味着
> food'ToBasket (butter' 1 >> apple' 1 >> meat' 0.5)
Basket { butter = 1, apple = 0, meat = (0, 0.0)}
因此,不是在编译时检查 Butter'
是结构中的最后一项,而是在遇到 Butter'
时实质上会短路,就像 Nothing
一样短路Maybe
计算。
我有以下数据类型:
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 -> Basket 允许我将 Food 类型的值转换为 Basket 类型的值。
所以如果我声明这个 Food 实例:
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" 部分,即使很难我发现很难在括号内使用递归。这就是我使用 anotherBasket 和 where 子句的原因,这非常令人困惑。 正如我担心的那样,我无法对 Meat 做同样的事情。 (它还没有递归)
我想用模式匹配来解析整个函数。
代码看起来有点混乱。您可能想要做的是定义一个将两个篮子相加的函数:
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)
这里有一个更完整的解决方案:
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)
扩展 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 foodItemToBasket
和 mconcat
:
foodToBasket :: Food -> Basket
foodToBasket = mconcat . map foodItemToBasket
现在将每个项目转换为篮子变得更加简单并且不包含递归,并且将这些篮子组合在一起的行为由 mconcat
处理,这是 [=22 提供的更通用的函数=].
但是,如果您确实需要递归数据结构,您实际上可以通过转向 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 实例,让您可以简单地列出一个人使用 do 表示法拥有的不同项目,然后 food'ToBasket
将此 monad 结构处理 "interpreting" 到一个篮子中。它确实意味着
> food'ToBasket (butter' 1 >> apple' 1 >> meat' 0.5)
Basket { butter = 1, apple = 0, meat = (0, 0.0)}
因此,不是在编译时检查 Butter'
是结构中的最后一项,而是在遇到 Butter'
时实质上会短路,就像 Nothing
一样短路Maybe
计算。