2012-08-28 6 views
5

C'est ma solution à l'exercice de Yaht:Est-ce que cet échantillon de haskell peut être plus court?

Exercice 4.6 Écrire un Tuple datatype qui peut contenir un, deux, trois ou quatre éléments, selon le constructeur (qui est, il devrait y avoir quatre constructeurs , un pour chaque nombre d'arguments). Fournissez également fonctions tuple1 à tuple4 qui prennent une ligne et renvoient juste la valeur dans cette position, ou Nothing si le nombre est valide (par exemple, vous demandez le tuple4 sur un tuple ne contenant que deux éléments).

Quand j'ai écrit une première ligne que je me réjouissais de simplicité comparé à C#

 

    data Tuplex a b c d = Tuple1 a | Tuple2 a b | Tuple3 a b c | Tuple4 a b c d 

    -- class Tuplex<a,b,c,d> { 
    --  Tuplex(a p1){ _p1 = p1; } 
    --  Tuplex(a p1, b p2){ _p1 = p1; _p2 = p2; } 
    --  Tuplex(a p1, b p2, c p3){ _p1 = p1; _p2 = p2; _p3 = p3; } 
    --  Tuplex(a p1, b p2, c p3, d p4){ _p1 = p1; _p2 = p2; _p3 = p3; _p4 = p4; } 
    --  public Nullable<a> _p1; 
    --  public Nullable<b> _p2; 
    --  public Nullable<c> _p3; 
    --  public Nullable<d> _p4; 
    -- } 

En C# Je peux accéder à tous les domaines sans problème, mais ici je devrais écrire un « accesseurs », non? Et la quantité de code ici me rend triste.

Puis-je avoir un code plus court ici?

 

    tuple1 ∷ Tuplex a b c d → Maybe a 
    tuple2 ∷ Tuplex a b c d → Maybe b 
    tuple3 ∷ Tuplex a b c d → Maybe c 
    tuple4 ∷ Tuplex a b c d → Maybe d 
    tuple1 (Tuple1 a) = Just a 
    tuple1 (Tuple2 a b) = Just a 
    tuple1 (Tuple3 a b c) = Just a 
    tuple1 (Tuple4 a b c d) = Just a 
    tuple2 (Tuple1 a) = Nothing 
    tuple2 (Tuple2 a b) = Just b 
    tuple2 (Tuple3 a b c) = Just b 
    tuple2 (Tuple4 a b c d) = Just b 
    tuple3 (Tuple1 a) = Nothing 
    tuple3 (Tuple2 a b) = Nothing 
    tuple3 (Tuple3 a b c) = Just c 
    tuple3 (Tuple4 a b c d) = Just c 
    tuple4 (Tuple1 a) = Nothing 
    tuple4 (Tuple2 a b) = Nothing 
    tuple4 (Tuple3 a b c) = Nothing 
    tuple4 (Tuple4 a b c d) = Just d 

    -- unit tests 
    prop_tx1 = tuple1 (Tuple1 4) ≡ Just 4 
    prop_tx2 = tuple1 (Tuple2 4 'q') ≡ Just 4 
    prop_tx3 = tuple2 (Tuple1 4) ≡ (Nothing ∷ Maybe Char) 
    prop_tx4 = tuple2 (Tuple2 4 'q') ≡ Just 'q' 

+0

BTW un des avantages C# ici est peut-être par rapport à Nullable . En C# nous avons une syntaxe spéciale (?postfix) et complètement transparent (mais toujours contrôlable) entre T? et T (int? et int). Même pendant le déballage, je peux avoir l'objet x = 5; var y = (int?) x. Ne pas mentionner la conversion inutile de int b = 5; à int? c = b; –

+0

Oh, tant de réponses et de façons ... Et vous savez quoi? Rien n'est assez lisible si l'on prend en compte une quantité de connaissances que l'auteur donne dans YAHT à ce point d'exercice, sauf pour la réponse de Daniel 1 ... Bien sûr, une lecture plus approfondie peut éclairer 'où' ou '>> =' '> =>' opérateurs ... Mais même cette réponse est assez complexe, je pense, je ne devinerais pas que par moi-même, semble difficile :) Merci pour les réponses beaucoup. –

Répondre

7

Voici une façon de centraliser votre motif.

unTuplex f1 f2 f3 f4 t = case t of 
    Tuple1 a  -> f1 a 
    Tuple2 a b  -> f2 a b 
    Tuple3 a b c -> f3 a b c 
    Tuple4 a b c d -> f4 a b c d 

tuple1 = unTuplex (\a -> Just a) (\a _ -> Just a) (\a _ _ -> Just a) (\a _ _ _ -> Just a) 
tuple2 = unTuplex (\_ -> Nothing) (\_ b -> Just b) (\_ b _ -> Just b) (\_ b _ _ -> Just b) 
tuple3 = unTuplex (\_ -> Nothing) (\_ _ -> Nothing) (\_ _ c -> Just c) (\_ _ c _ -> Just c) 
tuple4 = unTuplex (\_ -> Nothing) (\_ _ -> Nothing) (\_ _ _ -> Nothing) (\_ _ _ d -> Just d) 

Alternativement, vous pouvez explicitement exprimer la structure imbriquée:

{-# LANGUAGE NoMonomorphismRestriction #-} 
data DONE = DONE -- could just use(), but this is a pretty descriptive name 
type Tuplex a b c d = Maybe (a, Maybe (b, Maybe (c, Maybe (d, DONE)))) 

tuple1 x = x >>= return . fst -- or tuple1 = fmap fst 
tuple2 x = x >>= tuple1 . snd 
tuple3 x = x >>= tuple2 . snd 
tuple4 x = x >>= tuple3 . snd 

Alors tuple1 a (entre autres) le type Tuplex a b c d -> Maybe a, et jusqu'à tuple4 qui a (encore une fois, entre autres) le type Tuplex a b c d -> Maybe d.

edit: ... en réalité, ceci suggère une continuation alternative de la première approche.

import Control.Monad 

decrement :: Tuplex a b c d -> Maybe (Tuplex b c d t) 
decrement (Tuple1 a) = Nothing 
decrement (Tuple2 a b) = Just (Tuple1 b) 
decrement (Tuple3 a b c) = Just (Tuple2 b c) 
decrement (Tuple4 a b c d) = Just (Tuple3 b c d) 

zero :: Tuplex a b c d -> a 
zero (Tuple1 a) = a 
zero (Tuple2 a b) = a 
zero (Tuple3 a b c) = a 
zero (Tuple4 a b c d) = a 

tuple1 = Just . zero 
tuple2 = decrement >=> tuple1 
tuple3 = decrement >=> tuple2 
tuple4 = decrement >=> tuple3 
1

Donnez simplement le nom de vos champs tuples!

data Tuplex a b c d = Tuple1 { tuple1 :: a } 
        | Tuple2 { tuple1 :: a 
          , tuple2 :: b } 
        | Tuple3 { tuple1 :: a 
          , tuple2 :: b 
          , tuple3 :: c } 
        | Tuple4 { tuple1 :: a 
          , tuple2 :: b 
          , tuple3 :: c 
          , tuple4 :: d } 

Et par conséquent, vous avez des fonctions avec les types de:

tuple1 :: Tuplex a b c d -> a 
tuple2 :: Tuplex a b c d -> b 
-- etc 

Utilisation des noms de champs d'enregistrements comme celui-ci est en fait moins commun que vous pourriez attendre dans Haskell en raison de la facilité de mise en correspondance de motif et , dans au moins certains milieux, la popularité de l'extension RecordWildCard qui vous permet de faire des choses comme:

function (Tuples3 {..}) = 
-- now you have variables tuple1 :: a, tuple2 :: b, etc. 

(lors de l'utilisation des jokers records, il pourrait être Bett er pour nommer vos champs de tuple quelque chose d'un peu plus simple, comme tupA, tupB, tupC, tupD)

+7

L'autre problème, au moins dans cet exemple, est que les fonctions d'accès sont partielles. Quelque chose comme 'tuple2 (Tuple1" foo ")' se casserait. Dans ce cas particulier - et dans la plupart des codes normaux, j'imagine - vous voulez une manière plus élégante de gérer les accès invalides comme cela (par exemple renvoyer un 'Maybe' comme spécifié dans la question). –

+0

Ah, je n'ai pas remarqué qu'il utilisait 'Maybe'. L'outil 'derive' peut probablement faire cela et sinon il devrait être corrigé. De plus, il y a peu de différence entre son utilisation et une donnée 'Tup abcd = Tup {tA :: (Peut-être a), tB :: (Peut-être b) ...}' (plus quelques fonctions/constructeurs auxiliaires) alors peut-être que ce serait une meilleure solution. –

1
import Safe (atMay) -- from the 'safe' package 

toList (Tuple1 a) = [a] 
toList (Tuple2 a b) = [a, b] 
toList (Tuple3 a b c) = [a, b, c] 
toList (Tuple4 a b c d) = [a, b, c, d] 

tuple n t = atMay (toList t) n 

[tuple1, tuple2, tuple3, tuple4] = map tuple [1..4] 

Edit: Vitus soulignent à juste titre que cela ne fonctionne que pour un tuple homogène, donc ce n'est pas une réponse correcte. Dans ce cas, je m'en remets à la réponse de Daniel.

+1

Si vous ne voulez pas utiliser un paquet externe pour une raison quelconque, vous pouvez aussi faire quelque chose comme 'listToMaybe. drop n'. –

+1

Cela ne fonctionne que si tous les éléments de tuple ont le même type. – Landei

+1

Rappelez-vous que 'tuple1 :: TupleX a a a a -> Peut-être a' et pas le plus générique' TupleX a b c d -> Peut-être a'. – Vitus

7

Je voudrais essayer de le garder mort simple:

data Tuplex a b c d = Tuple1 a | Tuple2 a b | Tuple3 a b c | Tuple4 a b c d 

toMaybes (Tuple1 p)  = (Just p, Nothing, Nothing, Nothing) 
toMaybes (Tuple2 p q)  = (Just p, Just q, Nothing, Nothing) 
toMaybes (Tuple3 p q r) = (Just p, Just q, Just r, Nothing) 
toMaybes (Tuple4 p q r s) = (Just p, Just q, Just r, Just s) 

tuple1 t = p where (p,_,_,_) = toMaybes t 
tuple2 t = q where (_,q,_,_) = toMaybes t 
tuple3 t = r where (_,_,r,_) = toMaybes t 
tuple4 t = s where (_,_,_,s) = toMaybes t 
+0

Cela semble plus simple et compréhensible, mais au moment de lire ce livre, je ne suis pas familier avec l'opérateur 'where' –

+0

Ceci est la même chose que 'tuple1 t = let (p, _, _, _) = toMaybes t dans p'. Dans la plupart des cas, vous pouvez voir 'where' comme une sorte de" lettred "let'". Il y a quelques différences concernant des portées etc., cependant. – Landei

Questions connexes