2009-06-14 11 views
207

Je suis tombé sur la définition suivante que j'essaie d'apprendre Haskell en utilisant un vrai projet pour le conduire. Je ne comprends pas ce que signifie le point d'exclamation devant chaque argument et mes livres ne semblent pas le mentionner.Que signifie le point d'exclamation dans une déclaration Haskell?

data MidiMessage = MidiMessage !Int !MidiMessage 
+12

Je suspecte que ceci puisse être une question très commune; Je me souviens clairement me poser des questions sur la même chose moi-même, il y a longtemps. –

Répondre

255

C'est une déclaration de rigueur. Fondamentalement, cela signifie qu'il doit être évalué à ce qu'on appelle une «forme de tête normale faible» lorsque la valeur de la structure de données est créée. Regardons un exemple, afin que nous puissions voir exactement ce que cela signifie:

data Foo = Foo Int Int !Int !(Maybe Int) 

f = Foo (2+2) (3+3) (4+4) (Just (5+5)) 

La fonction f ci-dessus lors de son évaluation, retournera un « thunk »: à savoir, le code à exécuter pour déterminer sa valeur . À ce stade, un Foo n'existe même pas encore, juste le code.

Mais à un certain moment, quelqu'un peut essayer de regarder à l'intérieur, probablement par une correspondance de motif:

case f of 
    Foo 0 _ _ _ -> "first arg is zero" 
    _   -> "first arge is something else" 

Cela va exécuter du code assez pour faire ce dont il a besoin, et pas plus. Donc, il va créer un Foo avec quatre paramètres (parce que vous ne pouvez pas regarder à l'intérieur sans qu'il existe). Le premier, puisque nous le testons, nous devons évaluer tout le chemin à 4, où nous nous rendons compte qu'il ne correspond pas.

La seconde n'a pas besoin d'être évaluée, car nous ne la testons pas. Ainsi, plutôt que 6 étant stocké dans cet emplacement de mémoire, nous allons simplement stocker le code pour une évaluation ultérieure possible, (3+3). Cela deviendra un 6 seulement si quelqu'un le regarde.

Le troisième paramètre, cependant, a un ! en face de lui, donc est strictement évalué: (4+4) est exécuté, et 8 est stocké dans cet emplacement de mémoire.

Le quatrième paramètre est également évalué strictement. Mais voici où cela devient un peu difficile: nous évaluons pas complètement, mais seulement à la forme de la tête normale faible. Cela signifie que nous déterminons si c'est Nothing ou Just quelque chose, et stockons cela, mais nous n'allons pas plus loin. Cela signifie que nous ne stockons pas Just 10 mais réellement Just (5+5), laissant le thunk à l'intérieur non évalué. C'est important à savoir, bien que je pense que toutes les implications de ceci vont au-delà de la portée de cette question.

Vous pouvez annoter arguments de la fonction de la même manière, si vous activez l'extension de la langue BangPatterns:

f x !y = x*y 

f (1+1) (2+2) retournera thunk (1+1)*4.

+12

Ceci est très utile. Je ne peux m'empêcher de me demander si la terminologie de Haskell rend les choses plus compliquées à comprendre avec des termes tels que «forme de tête normale faible», «strict», etc. Si je vous comprends bien, ça sonne comme le! L'opérateur signifie simplement stocker la valeur évaluée d'une expression plutôt que de stocker un bloc anonyme pour l'évaluer plus tard. Est-ce une interprétation raisonnable ou y a-t-il quelque chose de plus? – David

+61

@ David La question est de savoir jusqu'où pour évaluer la valeur. Forme normale faible-tête signifie: évaluez-le jusqu'à ce que vous atteigniez le constructeur le plus à l'extérieur. La forme normale signifie évaluer la valeur entière jusqu'à ce qu'il ne reste plus de composants non évalués. Parce que Haskell permet toutes sortes de niveaux de profondeur d'évaluation, il a une terminologie riche pour décrire cela. Vous n'avez pas tendance à trouver ces distinctions dans les langages qui ne prennent en charge que la sémantique call-by-value. –

+7

@ David: J'ai écrit une explication plus approfondie ici: [Haskell: Quel est faible normal Head Forme] (http://stackoverflow.com/questions/6872898/haskell-what-is-weak-head-normal- formulaire/6889335 # 6889335). Bien que je ne mentionne pas les modèles de bang ou les annotations de rigueur, ils équivalent à utiliser 'seq'. – hammar

23

Je crois qu'il s'agit d'une annotation de rigueur.

Haskell est un paresseux langage fonctionnel, mais parfois, la surcharge de la paresse peut être trop ou inutile. Donc, pour faire face à cela, vous pouvez demander au compilateur d'évaluer complètement les arguments d'une fonction au lieu d'analyser les thunks.

Vous trouverez plus d'informations sur cette page: Performance/Strictness.

+0

Hmm, l'exemple sur cette page que vous avez référé semble parler de l'utilisation de! lors de l'appel d'une fonction. Mais cela semble différent de le mettre dans une déclaration de type. Qu'est-ce que je rate? – David

+0

La création d'une instance d'un type est également une expression. Vous pouvez considérer les constructeurs de type comme des fonctions qui renvoient de nouvelles instances du type spécifié, étant donné les arguments fournis. –

+1

En fait, à toutes fins pratiques, les constructeurs de type * sont des fonctions *; vous pouvez les appliquer partiellement, les transmettre à d'autres fonctions (par exemple, 'map Just [1,2,3]' pour obtenir [Just 1, Just 2, Just 3]) et ainsi de suite. Je trouve qu'il est utile de penser à la capacité de modéliser avec eux ainsi qu'une installation complètement indépendante. –

67

Un moyen simple de voir la différence entre les arguments constructeurs strict et non strict est de savoir comment ils se comportent lorsqu'ils ne sont pas définis.Compte tenu

data Foo = Foo Int !Int 

first (Foo x _) = x 
second (Foo _ y) = y 

Depuis l'argument non strict est pas évalué par second, en passant undefined ne pose pas de problème:

> second (Foo undefined 1) 
1 

Mais l'argument stricte ne peut pas être undefined, même si nous n'utilisons pas la valeur:

> first (Foo 1 undefined) 
*** Exception: Prelude.undefined 
+0

C'est mieux que la réponse de Curt Sampson, car elle explique en fait les effets observables par l'utilisateur que le symbole '!' A, plutôt que de se plonger dans les détails de l'implémentation interne. –