Ответ 1
Проблема состоит в том, что списки в Haskell однородны (все элементы имеют один и тот же тип). И в [foo_a, foo_b]
вы пытаетесь создать список с двумя разными типами: Foo -> Int
и Foo -> Float
.
Одним из решений является перемещение show
внутри списка и создание списка Foo -> String
:
Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> fn x) [show . foo_a, show . foo_b]
"2 3.4"
((\fn -> fn x)
может быть записано как ($ x)
)
Другая возможность - создать тип данных для унификации нескольких типов, которые вы хотите поместить в список, имитируя гетерогенную коллекцию . В вашем случае это может быть примерно так:
{-# LANGUAGE ExistentialQuantification #-}
data Showable b = forall a . Show a => MkShowable (b -> a)
pack :: Show a => (b -> a) -> Showable b
pack = MkShowable
unpack :: Showable b -> b -> String
unpack (MkShowable f) = show . f
Затем вы можете сделать:
*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> unpack fn x) [pack foo_a, pack foo_b]
"2 3.4"
[Обновление]
Я играл с Data.Dynamic
, и он кажется более перспективным, чем тот, который я создал выше.
Начнем с:
{-# LANGUAGE DeriveDataTypeable #-}
import Control.Applicative
import Data.Dynamic
import Data.Maybe
data Foo = Foo {foo_a :: Int, foo_b :: Float}
deriving Typeable
get_a :: Dynamic -> Maybe (Foo -> Int)
get_a = fromDynamic
get_b :: Dynamic -> Maybe (Foo -> Float)
get_b = fromDynamic
getAsString :: Dynamic -> (Foo -> String)
getAsString dyn = fromJust $ (show .) <$> get_a dyn
<|> (show .) <$> get_b dyn
<|> error "Type mismatch"
В этом случае мы можем сделать:
*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> getAsString fn x) [toDyn foo_a, toDyn foo_b]
"2 3.4"
Кажется, нам пришлось написать больше кода для достижения того же результата, но на самом деле это дает нам гораздо большую гибкость. Хотя экзистенциальный тип был сфокусирован только на показе полей как String
, здесь мы не ограничиваемся этим. Что, если теперь мы хотим видеть все поля как Int
(в случае Float
мы хотим округлить значение)?
getAsInt :: Dynamic -> (Foo -> Int)
getAsInt dyn = fromJust $ get_a dyn
<|> (round .) <$> get_b dyn
<|> error "Type mismatch"
Теперь мы можем сделать:
*Main> let x = Foo 2 3.4
*Main> map (\fn -> getAsInt fn x) [toDyn foo_a, toDyn foo_b]
[2,3]