2010-06-18 9 views
4

Je me demande si une fonction IO() peut renvoyer un tuple parce que je voudrais sortir ces fonctions de cette fonction en tant qu'entrée pour une autre fonction.Tuple de retour d'entrée Haskell

investinput :: IO()->([Char], Int) 
investinput = do 
putStrLn "Enter Username : " 
username <- getLine 

putStrLn "Enter Invest Amount : " 
tempamount <- getLine 
let amount = show tempamount 
return (username, amount) 

Aidez-nous s'il vous plaît.

Merci.

+1

Essayez de supprimer la signature de type, en changeant 'show' en' read', et en ajoutant ':: Int' à la fin de la ligne où vous avez ajouté' read'. Puis chargez-le dans GHCi, essayez-le et demandez le type avec ': t'. Ce ne sera pas ce que tu as ici. –

+0

Merci cela fonctionne. – peterwkc

Répondre

8

IO dans Haskell ne fonctionne pas comme IO dans les langues auxquelles vous êtes habitué. Toutes les fonctions de Haskell doivent être pures: si une fonction f est appelée avec l'argument x, il ne doit y avoir aucune différence entre l'appeler une fois, deux fois ou une centaine de fois. Considérez ce que cela signifie pour IO, cependant. Naïvement, devrait avoir le type getLine :: String, ou peut-être getLine ::() -> String. (() est le type d'unité, dont la seule valeur est (), c'est un peu comme un type vide dans un langage C-like, mais il y en a une seule valeur.) Mais cela signifierait que chaque fois que vous écriviez getLine, devez retourner le même chaîne, ce qui n'est pas ce que vous voulez. C'est le but du type IO: encapsuler actions. Ces actions sont distinctes des fonctions; ils représentent impure calcul (bien qu'ils soient eux-mêmes purs). Une valeur de type IO a représente une action qui, lorsqu'elle est exécutée, renvoie une valeur de type a. Ainsi, getLine a le type getLine :: IO String: chaque fois que l'action est évaluée, un String est produit (en lisant à partir de l'utilisateur). De même, putStr a le type putStr :: String -> IO(); c'est une fonction qui prend une chaîne et renvoie une action qui, lorsqu'elle est exécutée, ne renvoie aucune information utile ... mais, en tant qu'effet secondaire, imprime quelque chose à l'écran. Vous essayez d'écrire une fonction de type IO() -> ([Char], Int). Ce serait une fonction qui a pris en entrée une action et retourné un tuple, qui est pas ce que vous voulez. Vous voulez une action IO (String, Int) -an qui, lorsqu'elle est exécutée, produit un tuple constitué d'une chaîne (qui est un synonyme de [Char]) et un nombre entier. Vous êtes presque là avec votre code actuel, aussi!Voici ce que vous aurez besoin à la place:

investinput :: IO (String, Int) 
investinput = do 
    putStrLn "Enter Username : " 
    username <- getLine 
    putStrLn "Enter Invest Amount : " 
    tempamount <- getLine 
    let amount = read tempamount 
    return (username, amount) 

Notez que je n'ai fait deux changements (et enlevé une ligne vide). D'abord, j'ai changé le type de la fonction, comme je l'ai dit plus haut. Deuxièmement, j'ai changé show en read. La fonction show a le type Show a => a -> String: c'est une fonction qui prend tout ce qui peut être affiché et produit une chaîne la représentant. Vous voulu read, qui a le type Read a => String -> a: donné une chaîne, il l'analyse et renvoie une valeur lisible.

L'autre élément que vous avez demandé concerne le retour d'un n-uplet (String, Int) au lieu d'une action IO (String, Int). Il n'y a pas de moyen pur de le faire; en d'autres termes, il n'y a pas de fonction pure IO a -> a. Pourquoi est-ce? Parce que IO a représente une action impure qui dépend du monde réel. Si nous avions une telle fonction impossibleRunIO :: IO a -> a, alors nous voudrions que ce soit le cas impossibleRunIO getLine == impossibleRunIO getLine, puisque la fonction doit être pure. Mais cela est inutile, car nous voulons que impossibleRunIO puisse réellement interagir avec le monde réel! Ainsi, cette fonction pure est impossible. Tout ce qui entre IO ne peut jamais partir. C'est ce que fait return: c'est une fonction avec, dans ce cas , le type return :: a -> IO a, qui vous permet de placer des valeurs pures dans IO. Pour toute x, return x est une action qui, lorsqu'elle est exécutée, produit toujours x. C'est pourquoi vous devez terminer votre bloc do avec le return: username est une valeur pure que vous avez extraite d'une action, et en tant que telle n'est visible que dans le bloc do. Vous devez le soulever en IO avant que le monde extérieur puisse le voir. La même chose est vraie de amount/tempamount.

Et juste pour la complétude: il y a une théorie globale derrière ceci qui la lie ensemble. Mais ce n'est pas du tout nécessaire pour commencer la programmation Haskell. Ce que je recommanderais de faire est structurer la plupart de votre code en tant que fonctions pures qui replient, broches et mutilent vos données. Ensuite, construisez une couche avant mince (aussi mince que possible) IO qui interagit avec lesdites fonctions. Vous serez surpris du peu d'E/S dont vous avez besoin!

1: Il a en fait un type plus général, mais ce n'est pas pertinent pour le moment.

+0

En fait, ce que je veux est quelque chose comme ce test = savefile investinput La passe chaîne IO d'entrée investir est à savefile pour sauver investinput :: IO ([Char], Int) principaux = faire (nom d'utilisateur, montant) <- investinput Si je fais comme ceci le principal a toujours le type d'IO() qui n'est pas je veux. La fonction de test doit avoir un type différent. – peterwkc

+1

Je ne peux pas vraiment suivre ce que vous avez écrit, mais je vais essayer d'y répondre. Vous avez dit que vous ne voulez pas que 'main' ait le type' IO() '. Pensons à ce que 'principal' est. Il représente l'ensemble de votre programme, il doit donc s'agir d'une sorte d'action impure; et puisque c'est tout votre programme, il ne peut rien rendre utile. Ainsi, il * doit * avoir le type 'IO()'. Et lui demander d'avoir un autre type revient à demander à votre compilateur C d'accepter 'double main (char * x, void (* y)())' c'est interdit. Si ce n'est pas encore assez d'information, pourquoi ne pas éditer votre question pour expliquer ce que vous essayez de faire? –

+2

Votre point est bien sûr correct, mais comme un peu de trivia, 'main' est seulement nécessaire pour avoir" type 'IO t' pour un certain type' t' "selon le rapport. Donc 'main = getLine' est un programme valide (et inutile). Cela n'a absolument rien à voir avec la question de peterwkc, mais c'est un drôle de petit fait qui m'a surpris quand je l'ai trouvé. –

4

Oui, vous y êtes presque, mais je pense que vous voulez la signature:

investinput :: IO ([Char], Int) 

... puis de la fonction d'appel que vous pouvez faire quelque chose comme:

main = do 
    (username, amount) <- investinput 
    .... 

Je pense que vous veux lire tempamount plutôt que de montrer cependant.

+0

Merci cela fonctionne mais pourquoi cette signature de type ne fonctionne pas. investinput :: IO() -> ([Char], Int) – peterwkc

+0

J'ai besoin de retourner le tuple plutôt que de retourner le tuple IO. nvestinput :: IO ([Char], Int) investinput = ne \t putStrLn "Entr.NomUtilis:" \t nom d'utilisateur <- getLine \t \t putStrLn "Entrez Invest Montant:" \t tempamount <- getLine \t let amount = read tempamount :: Int \t putStrLn "" \t retour (nom d'utilisateur, montant) – peterwkc

+2

J'ai commencé à essayer de décrire ce qui se passe ici en utilisant l'analogie de la boîte d'E/S, mais je ne pense pas qu'une description rapide soit vraiment utile. Il n'y a pas de substitut à la lecture des monades pour comprendre pourquoi IO ([Char], Int) est le type. http://book.realworldhaskell.org/read/ est une excellente ressource pour cela – Keith

2

Une fonction IO qui produit un tuple aurait de type IO (a, b), dans ce cas:

investinput :: IO ([Char], Int) 

Une signature de IO() -> ([Char], Int) voudrait dire que la fonction prend un paramètre de type IO() et produit un tuple de celle qui ce n'est pas ce que tu veux.

Généralement il n'y a aucune restriction sur les types qu'une fonction IO (ou une fonction dans une monade différente) peut retourner, vous pouvez choisir les types comme vous le souhaitez.

+0

Une signature de IO() -> ([Char], Int) signifierait que la fonction prend un paramètre de type IO() et produit un tuple de cela, ce qui n'est pas ce que vous voulez. J'ai besoin d'une entrée IO et d'un tuple de retour. Merci. C'est ce que je veux. – peterwkc

+0

Non, 'IO() -> ...' signifierait que vous donnez une fonction d'E/S * comme paramètre * à 'investinput', comme si vous l'appeliez' 'investinput (print 1)'. Que votre fonction utilise une autre fonction comme 'getLine' qui lit depuis la console n'est pas reflétée dans la signature de type. La signature de type indique simplement quels types de paramètres un appel de fonction attend et quel type de valeur de retour il va produire. – sth

+0

La raison pour laquelle j'ai besoin de l'IO() -> ([Char, Int]) est parce que j'ai une autre fonction qui attend un tuple pour faire le calcul. J'ai également vu un exemple comme cet index lifM (readFile "input.txt") liftM a cette signature: liftM :: Monad m => (a -> b) -> ma -> mb Il faut un non -monadique et le transforme en fonction monadique. fmap index $ readFile "entrée.txt" readFile "entrée.txt" >> = retour. index Aidez s'il vous plaît. – peterwkc

1

La réponse à votre question au sujet de renvoyer (String, Int) plutôt que IO (String, Int) est simple: vous ne pouvez pas. Une fois que vous êtes au IO, vous êtes coincé. Cela fait partie de ce que cela signifie quand les gens disent que Haskell est une langue «pure».

Ce que vous voulez faire est similaire à ce que vous faites déjà ici avec getLine. Le type de getLine est IO String. Lorsque vous écrivez username <- getLine, vous prenez en effet le String sur le IO String, mais cela n'est possible que parce que vous êtes dans l'expression do. Vous pouvez faire exactement le même genre de chose avec investinput qu'avec getLine.Voici un exemple de la façon dont vous pouvez utiliser investinput dans votre fonction main:

main = do 
    (name, amount) <- investinput 
    putStrLn $ name ++ " invested $" ++ show amount ++ "." 

Puisque vous mentionnez liftM dans un commentaire, voici une version complète de travail qui fait la même chose en utilisant liftM et l'opérateur de liaison inverse (=<<) au lieu de do notation:

import Control.Monad (liftM) 

investinput :: IO (String, Int) 
investinput = do 
    putStrLn "Enter Username : " 
    username <- getLine 
    putStrLn "Enter Invest Amount : " 
    amount <- liftM read getLine -- We can also use liftM to get rid of tempamount. 
    return (username, amount) 

summary :: (String, Int) -> String 
summary (name, amount) = name ++ " invested $" ++ show amount ++ "." 

main = putStrLn =<< liftM summary investinput 

Cela montre comment vous pouvez utiliser investinput avec « une autre fonction qui attend un tuple ».

+0

J'essaie le code mais il n'est pas compilé. importation Monad - Recevoir entrée IO faire quelque chose résumé :: ([Char], [Char]) -> Résumé Chaîne (nom, montant) = putname ++ \t "investi $" ++ montant + + "." investinput :: IO() investinput = ne \t putStrLn "Entrez Nom d'utilisateur:" \t nom d'utilisateur <- getLine \t \t putStrLn "Entrez Invest Montant:" \t tempamount <- getLine \t putStrLn "" main = putStrLn = << liftM résumé investinput – peterwkc

+0

J'ai modifié pour inclure un exemple compilable complet. –

+0

Merci. Pouvez-vous expliquer ces lignes de code? liftM résumé investinput Qu'est-ce que liftM? Il dit promouvoir une fonction en monade. Ce que cela signifie ? – peterwkc