Synthèse sur des listes de niveaux arbitraires d'imbrication dans F #

10

J'essaie de créer une fonction F # qui renverra la somme d'une liste de ints d'imbrication arbitraire. C'est à dire. cela fonctionnera pour a list<int>, a list<list<int>>et a list<list<list<list<list<list<int>>>>>>.

En Haskell, j'écrirais quelque chose comme:

class HasSum a where
    getSum :: a -> Integer

instance HasSum Integer where
    getSum = id

instance HasSum a => HasSum [a] where
    getSum = sum . map getSum

ce qui me permettrait de faire:

list :: a -> [a]
list = replicate 6

nestedList :: [[[[[[[[[[Integer]]]]]]]]]]
nestedList =
    list $ list $ list $ list $ list $
    list $ list $ list $ list $ list (1 :: Integer)

sumNestedList :: Integer
sumNestedList = getSum nestedList

Comment puis-je y parvenir en F #?

runeks
la source
1
Je ne connais pas assez F # - je ne sais pas s'il prend en charge quelque chose comme les classes de Haskell. Dans le pire des cas, vous devriez pouvoir passer des dictionnaires explicites même si ce n'est pas aussi pratique que dans Haskell où le compilateur déduit les bons dictionnaires pour vous. Le code F # dans ce cas serait quelque chose comme getSum (dictList (dictList (..... (dictList dictInt)))) nestedListoù le nombre de dictListcorrespond au nombre de []dans le type de nestedList.
chi
Pourriez-vous rendre ce code haskell exécutable sur un REPL?
Filipe Carvalho
c'est parti
karakfa
F # n'a pas de classes de types ( github.com/fsharp/fslang-suggestions/issues/243 ). J'ai essayé l'astuce de surcharge de l'opérateur qui en théorie pourrait fonctionner mais j'ai juste réussi à planter le compilateur mais peut-être pouvez-vous faire quelque chose de l'astuce: stackoverflow.com/a/8376001/418488
Juste un autre métaprogrammeur le
2
Je ne peux pas imaginer de base de code F # réaliste où vous en auriez besoin. Quelle était votre motivation pour faire cela? Je changerais probablement la conception afin que vous n'entriez pas dans une situation comme celle-ci - cela rendra probablement votre code F # meilleur de toute façon.
Tomas Petricek

Réponses:

4

MISE À JOUR

J'ai trouvé une version plus simple utilisant un opérateur ($)au lieu d'un membre. Inspiré par https://stackoverflow.com/a/7224269/4550898 :

type SumOperations = SumOperations 

let inline getSum b = SumOperations $ b // <-- puting this here avoids defaulting to int

type SumOperations with
    static member inline ($) (SumOperations, x  : int     ) = x 
    static member inline ($) (SumOperations, xl : _   list) = xl |> List.sumBy getSum

Le reste de l'explication s'applique toujours et c'est utile ...

J'ai trouvé un moyen de le rendre possible:

let inline getSum0< ^t, ^a when (^t or ^a) : (static member Sum : ^a -> int)> a : int = 
    ((^t or ^a) : (static member Sum : ^a -> int) a)

type SumOperations =
    static member inline Sum( x : float   ) = int x
    static member inline Sum( x : int     ) =  x 
    static member inline Sum(lx : _   list) = lx |> List.sumBy getSum0<SumOperations, _>

let inline getSum x = getSum0<SumOperations, _> x

2                  |> getSum |> printfn "%d" // = 2
[ 2 ; 1 ]          |> getSum |> printfn "%d" // = 3
[[2; 3] ; [4; 5] ] |> getSum |> printfn "%d" // = 14

Exécuter votre exemple:

let list v = List.replicate 6 v

1
|> list |> list |> list |> list |> list
|> list |> list |> list |> list |> list
|> getSum |> printfn "%d" // = 60466176

Ceci est basé sur l'utilisation de SRTP avec des contraintes de membre:, static member Sumla contrainte requiert que le type ait un membre appelé Sum qui renvoie un int. Lors de l'utilisation de SRTP, les fonctions génériques doivent l'être inline.

Ce n'est pas la partie difficile. La partie difficile est « ajoutant » Summembre à un type existant comme intet ce Listqui est interdit. Mais, nous pouvons l'ajouter à un nouveau type SumOperationset l'inclure dans la contrainte (^t or ^a)^tva toujours être SumOperations.

  • getSum0déclare la Sumcontrainte de membre et l'invoque.
  • getSum passe SumOperationscomme premier paramètre de type àgetSum0

La ligne a static member inline Sum(x : float ) = int xété ajoutée pour convaincre le compilateur d'utiliser un appel de fonction dynamique générique et pas seulement par défaut static member inline Sum(x : int )lors de l'appelList.sumBy

Comme vous pouvez le voir, c'est un peu compliqué, la syntaxe est complexe et il était nécessaire de contourner certaines bizarreries sur le compilateur mais à la fin c'était possible.

Cette méthode peut être étendue pour fonctionner avec des tableaux, des tuples, des options, etc. ou toute combinaison de ceux-ci en ajoutant plus de définitions à SumOperations:

type SumOperations with
    static member inline ($) (SumOperations, lx : _   []  ) = lx |> Array.sumBy getSum
    static member inline ($) (SumOperations, a  : ^a * ^b ) = match a with a, b -> getSum a + getSum b 
    static member inline ($) (SumOperations, ox : _ option) = ox |> Option.map getSum |> Option.defaultValue 0

(Some 3, [| 2 ; 1 |]) |> getSum |> printfn "%d" // = 6

https://dotnetfiddle.net/03rVWT

AMières
la source
C'est une excellente solution! mais pourquoi pas juste récursivité ou pli?
s952163
4
La récursivité et le pli ne peuvent pas gérer différents types. Lorsqu'une fonction générique récursive est instanciée, elle fixe le type des paramètres. Dans ce cas , chaque appel à Sumse fait avec un type plus simple: Sum<int list list list>, Sum<int list list>, Sum<int list>, Sum<int>.
AMieres
2

Voici la version d'exécution, qui fonctionnerait avec toutes les collections .net. Cependant, échange les erreurs du compilateur dans la réponse d' AMières pour les exceptions d'exécution et AMières est 36 fois plus rapide également.

let list v = List.replicate 6 v

let rec getSum (input:IEnumerable) =
    match input with
    | :? IEnumerable<int> as l -> l |> Seq.sum
    | e -> 
        e 
        |> Seq.cast<IEnumerable> // will runtime exception if not nested IEnumerable Types
        |> Seq.sumBy getSum


1 |> list |> list |> list |> list |> list
|> list |> list |> list |> list |> list |> getSum // = 60466176

Repères

|    Method |        Mean |     Error |    StdDev |
|---------- |------------:|----------:|----------:|
| WeirdSumC |    76.09 ms |  0.398 ms |  0.373 ms |
| WeirdSumR | 2,779.98 ms | 22.849 ms | 21.373 ms |

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ms   : 1 Millisecond (0.001 sec)
jbtule
la source
1
Il fonctionne bien, bien qu'il soit sensiblement plus lent: son exécution 10 fois a pris 56 secondes, contre 1 seconde avec l'autre solution.
AMieres
Analyse comparative impressionnante! qu'avez-vous utilisé?
AMieres