À la recherche du language parfait

qui n'existe pas

a marqué ce sujet comme résolu.

Je commence tout juste Haskell et franchement, les erreurs me donnent pas envie :

1
2
3
4
5
6
7
8
> let a = [1, 2, 3]
> "foo" `elem` a

<interactive>:3:14:
    No instance for (Num [Char]) arising from a use of a
    In the second argument of elem, namely a
    In the expression: "foo" `elem` a
    In an equation for it: it = "foo" `elem` a

Je sais pourquoi ça ne fonctionne pas, mais ce n'est pas grâce au message d'erreur, c'est par logique. Il y a moyen de faire plus clair, niveau message d'erreur, du genre "a is a list of Num but "foo" is a list of Char". Pour moi (en temps que débutant, c'est probablement logique quand on connait le language), (Num [Char]), ça veut pas dire grand chose.

Ah, t’as juste pas eu de chance…

Quand tu ne précises pas le type d’un objet, Haskell essaye de le deviner lui-même, et quand il y a ambiguïté, il prend le type le plus général qu’il peut se permettre. Et en l’occurrence, ton let a = [1, 2, 3] ne lui permet pas de savoir quel type de nombres exactement ce sont censés être.

Donc il attribue à tes nombres le type « n’importe quel type numérique », ce qui s’écrit Num a => a. Tu verras ça quand tu seras arrivé au chapitre sur les classes de types. Et donc l’erreur te dit que le type [Char] n’implémente pas la classe de types Num.

Si tu précises explicitement le type de tes nombres, en faisant let a = [1, 2, 3] :: [Int], tu devrais obtenir une erreur beaucoup plus claire.

+2 -0

Le type [Char] est exactement le même que le type String, parce qu'en Haskell, une string est une liste de char. C'est une particularité du langage. Donc quand tu lis [Char], pense String (si c'est cohérent dans le contexte, bien-sûr).

Donc, on va dire que ton message c'est plutôt « No instance for (Num String) arising from a use of ‘a’ ».

Ta liste est de type "liste de t, avec t devant être un Num" (donc en gros, t peut être le type des entiers, des doubles, des complexes, …).
Tu dois, en premier argument de la fonction elem, avoir le type t, t étant un Num. Et le type String ("foo") n'est pas un Num. C'est ce que dit le message. Mais je te l'accorde, il y a une façon plus directe de le dire…

Ton premier argument doit être de type : Num t => t, donc un nombre (un entier, un flottant, un complexe, …).

1
2
3
4
5
6
1 `elem` [1, 2, 3]  -- Avec un Int
True
2 :+ 0 `elem` [1, 2, 3] -- Avec un Complex (2+0i)
True
3.14 `elem` [1, 2, 3] -- Avec un Double
False

Il y a ce bon cours pour OCaml mais tu peux aussi voir le Real World OCaml ou encore ce livre Le Langage Caml sur une version obsolète d'OCaml.

Haskell et OCaml sont différents et les deux communautés se côtoient énormément (l'un apprends toujours de l'autre et vice-versa). Factuellement, le système de type d'Haskell est beaucoup plus puissant (et amusant) qu'OCaml mais de l'autre côté, OCaml est plus en phase avec les réalités techniques et industrielles.

Cela ce voit dans les conférences où les deux mondes se retrouvent. On y trouve la présentation de concepts séduisants en Haskell tandis qu'OCaml présentent plus des implémentations pratiques. Ce qui fait qu'ils ont aussi des défauts. En général, un bon développeur dans le domaine des langages fonctionnels connait les deux et essaye d'en retirer le meilleur de chacun.

Merci pour les explications. Je comprends mieux.


Je commence juste Learn you a Haskell (je viens de finir la première partie "Starting Out") et je me suis dis que ça pourrais peut-être aider un futur auteur de vous décrire mon apprentissage du language.

L'auteur utilise des notions mathématiques pour expliquer les list comprehensions. Cependant, au tout début du tuto, il précise que celui-ci vise les gens venant de l'impératif. Ça lui aurait probablement pris moins de temps d'expliquer à travers une comparaison à un language proche de pseudo-code, genre Python.

1
2
3
4
5
6
[x*2 for x in range(7, 14) if x*2 > 20]
// ou pour que tout le monde comprenne
res = []
for x in range(7, 14):
    if x*2 > 20:
        res.append(x)
1
[ x*2 | x <- [7..13], x*2 > 20]

J'avoue que c'est plutôt agréable de pouvoir faire des trucs comme ça, ça change de l'impératif :

1
zip [0..] ["foo", "bar", "baz", "nurf"]

À par ça, je débute juste, mais j'ai juste l'impression que c'est un language pour les cool kids qui séparent leurs paramètres avec des espaces et non des virgules et qui utilisent une syntaxe à part que personne d'autre comprend. Après, je suis encore dans la partie Starting out, je me doute bien (et j'espère) qu'il n'y a pas que ça.

Edit : c'est ma manière pas très légère de dire que je ne vois (pas encore) l’intérêt.

Me tapez pas pour ce avis, c'est juste un premier ressenti, je devrais même pas me prononcer vu le peu que j'ai lu pour l'instant.


En général, un bon développeur dans le domaine des langages fonctionnels connait les deux et essaye d'en retirer le meilleur de chacun.

Dinosaure

Merci pour les liens. Pour l'instant, j'apprends Haskell, je verrais après pour le reste.


Edit : Pourquoi c et b valent 10 dans ce code ? Ça commence par l'inverse et c'est initialisé avant qu'on arrive à b <- [1..c] ?

1
 [ (a,b,c) | c <- [1..10], b <- [1..c], a <- [1..b], a^2 + b^2 == c^2] 

Ils ne valent pas 10, ils valent c (resp. b). En effet, on veut le couple $(a,b,c)$ tq $a < b$ et $a^2+b^2 = c^2$. Naturellement, cela implique que $a < b < c$ (il n'y aurait pas de solution (dans R, on s'entend, ça n'aurait pas trop de sens dans C de toute façon) sinon). Donc par conséquent, on a pas besoin de chercher des b qui auraient des valeurs supérieures à c (resp. a et c)

Il faut comprendre ça comme cela, donc

1
2
3
4
5
6
ans = []
for c in range(1,10):
    for b in range(1,c):
        for a in range(1,b):
            if a**2 + b**2 == c**2:
                ans.append((a,b,c))
+0 -0

À par ça, je débute juste, mais j'ai juste l'impression que c'est un language pour les cool kids qui séparent leurs paramètres avec des espaces et non des virgules et qui utilisent une syntaxe à part que personne d'autre comprend.

tleb

Tu l'as peut-être déjà vu, mais Haskell les fonctions sont currifiées, c'est-à-dire que tu peux en pratique appliquer moins de n arguments à une fonction qui en attend "en principe" n pour obtenir une fonction partiellement appliquée. On peut donc définir contains1 comme cela :

1
contains1 = elem 1

au lieu de :

1
contains1 xs = elem 1 xs

Il est courant de faire ça en Haskell, et je pense que ce serait moins commode avec une syntaxe classique à coup de parenthèses…

Cependant, la fonction d'un langage "classique" n'est pas exactement équivalente à celle de Haskell… Elle n'est pas currifiée. Conceptuellement, dans un langage classique le elem serait de type (x, [x]) -> Bool alors qu'en Haskell c'est x -> [x] -> Bool. La différence, c'est que tu n'as pas en fait clairement pas la notion d'arité dans la signature de type de Haskell.
Donc en Haskell, x -> [x] -> Bool peut être vu comme (x, [x]) -> Bool ou alors x -> ([x] -> Bool). Dans le premier cas, je mets un x et une liste de x et j'ai en retour un booléen. Dans le second cas, je mets un x et j'ai en retour une fonction qui à une liste de x associe un booléen (contains1).

Une syntaxe sans virgules-parenthèses sert donc plutôt bien la sémantique du langage.

EDIT : je précise que c'est aussi le cas en OCaml.

+3 -0

Tu l'as peut-être déjà vu, mais Haskell les fonctions sont currifiées, c'est-à-dire que tu peux en pratique appliquer moins de n arguments à une fonction qui en attend "en principe" n pour obtenir une fonction partiellement appliquée. On peut donc définir contains1 comme cela :

1
contains1 = elem 1

au lieu de :

1
contains1 xs = elem 1 xs

Il est courant de faire ça en Haskell, et je pense que ce serait moins commode avec une syntaxe classique à coup de parenthèses…

C'est peut-être naïf mais je suis personnellement convaincu que la syntaxe parenthésée a du bon aussi : au quotidien, il arrive souvent que l'on soit embêté par l'ordre des arguments quand on veut faire de l'application partielle. Et ̀flip c'est sympa, mais pas super élégant ou lisible à mon avis.

À par ça, je débute juste, mais j'ai juste l'impression que c'est un language pour les cool kids qui séparent leurs paramètres avec des espaces et non des virgules et qui utilisent une syntaxe à part que personne d'autre comprend.

tleb

Haskell (et OCaml) sont des langages avec une histoire différente des langages que tu connais déjà, donc oui ils "héritent" d'une syntaxe différente (celle de ML et propagent d'autres idiomes et d'autres approches. Il va te falloir longtemps avant de savoir faire en Haskell ce que tu savais faire avant, et tu te prendras la tête sur des questions aussi basiques que la syntaxe pour un petit moment encore – mais c'est rien comparé à ce que tu vas galérer le jour où tu voudras te lancer dans ton premier projet concret, parce que tu ne sauras pas l'organiser, par exemple.

Mais tout ça c'est une bonne chose, et pas seulement pour jouer les « cool kids ». En faisant ça tu prends du recul sur des notions que tu connais déjà, et tu en découvres d'autre. Les scientifiques qui font de la recherche en programmation ont beaucoup travaillé sur des langages de la famille ML, qui sont souvent mieux conçus (du point de vue d'un chercheur en programmation, bien sûr) que les langages plus industriels. Il y a beaucoup de concepts qui sont bien compris en Haskell, et qui finissent par passer (plus ou moins vite, et avec un succès variable) dans des langages plus industriels ; ça n'est pas inintéressant de les avoir étudiés dans un cadre un peu plus idéal avant de les mettre en pratique dans ces langages.

Accessoirement, si tu persévères tu pourras revenir dans un an avec des one-liners à faire pâlir n'importe quel programmeur Perl. C'est cool aussi.

@Lz36GQfANCkOchnWu2yv au niveau des one-liners (je les j'adore) ca serais quoi la logique pour sélectionner dans un tableau tout les éléments répondant à une conditions?

Par exemple en ruby, le tableau [2, 6, 7, 61, 9, 5, 9, 2, 31, 41] pour récupérer tous les éléments qui ont une valeur > 10, je ferais

1
array.select { |a| a > 10 }

Si jamais la facon de faire me plait, je m'y lance ce week end ! :D

En Haskell soit une liste de compréhension ou avec filter

1
2
3
4
5
-- liste de compréhension
[x | x <- [2, 6, 7, 61, 9, 5, 9, 2, 31, 41], x < 10]

-- filter
filter (10>) [2, 6, 7, 61, 9, 5, 9, 2, 31, 41]
+0 -0

Pourtant, la traduction littérale de brainfuck était correcte, non ? Même si ça paraît assez cru / vulgaire dit comme ça en Français…

Et si je peux rebondir sur ce langage, peut-être qu'il offre une certaine gymnastique des neurones ?

Dans un même registre, il y a le whitespace qui est tout aussi rigolo.

Après… Je ne pense pas que ces langages aient leur place dans l'industrie.

Pourtant, la traduction littérale de brainfuck était correcte, non ? Même si ça paraît assez cru / vulgaire dit comme ça en Français…

Ge0

La traduction littérale se rapproche plutôt de « casse-tête », le terme fuck étant ici employé pour exprimer l'idée que le cerveau est mis à rude épreuve par la syntaxe du langage.

Par ailleurs, si c'est plus le message de Cithoran qui posait à mon sens problème, j'ai préféré masquer les deux pour mettre fin au HS.

Après… Je ne pense pas que ces langages aient leur place dans l'industrie.

Ge0

C'est peu probable, en effet (quoique, sait-on jamais :p ).

+3 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte