Il problema è che gli elenchi in Haskell sono omogenei (tutti gli elementi hanno lo stesso tipo). E in [foo_a, foo_b]
si sta tentando di creare un elenco con due tipi diversi: Foo -> Int
e Foo -> Float
.
Una soluzione è quella di spostare il show
all'interno della lista e creare un elenco di 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)
può essere scritta come ($ x)
)
Un'altra possibilità è quella di creare un tipo di dati per unificare i diversi tipi che si desidera inserire in un elenco, imitando uno heterogeneous collection. Nel tuo caso, potrebbe essere qualcosa di simile:
{-# 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
Poi, si può fare:
*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> unpack fn x) [pack foo_a, pack foo_b]
"2 3.4"
[Update]
stavo giocando in giro con il Data.Dynamic
e sembra più promettente del tipo esistenziale che ho creato sopra.
Cominciamo con:
{-# 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"
In questo caso, possiamo fare:
*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> getAsString fn x) [toDyn foo_a, toDyn foo_b]
"2 3.4"
Sembra che abbiamo dovuto scrivere più codice per ottenere lo stesso risultato, ma in realtà ci dà molta più flessibilità. Mentre il tipo esistenziale era focalizzato solo nel mostrare i campi come String
, qui non siamo limitati a quello. Cosa succede se ora vogliamo vedere tutti i campi come Int
(nel caso di Float
vogliamo arrotondare il valore)?
getAsInt :: Dynamic -> (Foo -> Int)
getAsInt dyn = fromJust $ get_a dyn
<|> (round .) <$> get_b dyn
<|> error "Type mismatch"
Ora possiamo fare:
*Main> let x = Foo 2 3.4
*Main> map (\fn -> getAsInt fn x) [toDyn foo_a, toDyn foo_b]
[2,3]
Purtroppo non c'è modo semplice per fare quello che vuoi, perché diversi campi avranno tipi diversi e quindi non può essere gestito utilizzando gli elenchi e 'map'. Potrebbe esserci qualche soluzione compatta usando Template Haskell o qualche estensione più avanzata ... – Bakuriu