10 Stimmen

Übergabe von Listenelementen als Parameter an eine Curried-Funktion

Ich bin noch ein Haskell-Neuling. Ich weiß gerade genug, um mich mit falschen Annahmen in Schwierigkeiten zu bringen. Wenn ich die folgende Funktion habe...

quadsum w x y z = w+x+y+z

Ich möchte eine Funktion, die eine Liste nehmen kann, verwenden Sie jedes Element als Parameter in einer bestimmten Funktion wie quadsum und geben eine Curried-Funktion zur späteren Verwendung zurück.

Ich habe etwas ausprobiert, das den Effekt hat, dass...

magicalFunctionMaker f [] = (f)
magicalFunctionMaker f (x:xs) = magicalFunctionMaker (f x) xs

In der Hoffnung, dies tun zu können...

magicalFunctionMaker (quadsum) [4,3,2]

Eine Curried-Funktion erhalten wie...:

(((quadsum 4) 3) 2)

Alternativ können Sie auch anrufen:

magicalFunctionMaker (quadsum) [4,3,2,1]

Das Ergebnis ist...

((((quadsum 4) 3) 2) 1)

Ist dies möglich? Wie fehlgeleitet bin ich?

7voto

Paul Johnson Punkte 17182

Ich glaube, Sie missverstehen das Haskell-Typensystem.

Zunächst einmal ist Ihre "quadsum"-Funktion bereits kuriert. Sie können "quadsum 4 3" schreiben und erhalten eine Funktion zurück, die die 2 und die 1 als zusätzliche Argumente erwartet. Wenn du "quadsum 4 3 2 1" schreibst, ist das äquivalent zu "((((quadsum 4) 3) 2) 1)".

In Haskell hat eine Liste von Ganzzahlen einen anderen Typ als eine Ganzzahl oder ein Tupel wie "(4,3,2,1)". Angesichts dieser Tatsache ist es ziemlich schwierig zu verstehen, was Sie zu tun versuchen. Was soll passieren, wenn Sie dies schreiben?

magicalFunctionMaker quadsum [5,4,3,2,1]

Dein "magicalFunctionMaker" sieht aus wie "foldl", nur dass die Funktion, die du foldl gibst, nur zwei Argumente braucht. Du kannst also schreiben:

mySum = foldl (+) 0

Dies gibt eine Funktion zurück, die eine Liste nimmt und die Elemente summiert.

(Übrigens, wenn Sie das verstanden haben, lernen Sie den Unterschied zwischen foldl und foldr kennen.

Edita:

Nachdem ich Ihre Frage noch einmal gelesen habe, glaube ich, dass Sie etwas erreichen wollen:

magicalFunctionMaker quadSum [4,3,2,1] :: Integer
magicalFunctionMaker quadSum [4,3,2] :: Integer -> Integer

Das ist unmöglich, weil der Typ von magicalFunctionMaker von der Länge des Listenarguments abhängen würde, was eine dynamische Typisierung implizieren würde. Wie jemand sagte, können polyvariable Funktionen etwas Ähnliches tun (wenn auch nicht mit einem Listenargument), aber das erfordert ziemlich viele Milli-O-Beine der Typisierung.

4voto

John L Punkte 27757

Die Antwort von Paul Johnson trifft es ziemlich genau. Einfach tun

quadsum 4 3 2

und das Ergebnis ist die gewünschte Funktion mit dem Typ Integer -> Integer .

Aber manchmal ist das nicht gut genug. Manchmal erhält man Listen mit Zahlen, von denen man nicht weiß, wie lang sie sind, und man muss die Elemente auf seine Funktion anwenden. Das ist ein bisschen schwieriger. Das können Sie nicht tun:

magicalFunction2 f [] = f
magicalFunction2 f (x1:x2:xs) = f x1 x2

weil die Ergebnisse unterschiedliche Typen haben. Im ersten Fall benötigt das Ergebnis zwei Argumente, im zweiten Fall handelt es sich um eine vollständig angewendete Funktion, so dass keine weiteren Argumente zulässig sind. In diesem Fall ist es am besten, an der Liste und der ursprünglichen Funktion festzuhalten, bis genügend Argumente vorhanden sind:

type PAPFunc f a result = Either (f, [a]) result

magicfunc f xs = Left (f,xs)

apply (Left (f,xs)) ys = Left (f,xs++ys)
apply p _              = p

simp2 :: PAPFunc (a->a->b) a b -> PAPFunc (a->a->b) a b
simp2 (Left (f,(x1:x2:xs))) = Right (f x1 x2)
simp2 p = p

können Sie jetzt tun:

Main> let j = magicfunc (+) []
Main> let m = apply j [1]
Main> let n = apply m [2,3]

Main> either (const "unfinished") show $ simp2 m
"unfinished"
Main> either (const "unfinished") show $ simp2 n
"3"

Sie benötigen für jede Arität eine eigene Vereinfachungsfunktion, ein Problem, das sich am einfachsten mit Template Haskell lösen lässt.

Mit Listen von Argumenten (im Gegensatz zu einem Argument von Listen) neigt dazu, sehr umständlich in Haskell, weil die mehrere Ergebnisse alle unterschiedliche Typen haben, und es gibt sehr wenig Unterstützung für Sammlungen mit variabler Anzahl von unterschiedlich typisierten Argumente. Ich habe drei allgemeine Kategorien von Lösungen gesehen:

  1. Expliziter Code für jeden Fall separat zu codieren (wird schnell zu einer Menge Arbeit).

  2. Vorlage Haskell.

  3. Systemhackerei.

Meine Antwort bezieht sich hauptsächlich auf den Versuch, 1 weniger schmerzhaft zu machen. 2 und 3 sind nichts für schwache Nerven.

Edit: Es hat sich herausgestellt, dass es Folgendes gibt einige Pakete auf Hackage, die mit diesem Problem zu tun haben. Mit "iteratee":

import qualified Data.Iteratee as It
import Control.Applicative

magic4 f = f <$> It.head <*> It.head <*> It.head <*> It.head

liftedQuadsum = magic4 quadsum
-- liftedQuadsum is an iteratee, which is essentially an accumulating function
-- for a list of data

Main> p <- It.enumChunk (It.Chunk [1]) liftedQuadsum
Main> It.run p
*** Exception: EofException
Main> q <- It.enumChunk (It.Chunk [2,3,4]) p
Main> It.run q
10

Aber "Iteratee" und "Enumerator" sind wahrscheinlich zu viel des Guten.

3voto

kwantam Punkte 431

Ich bin auf das gleiche Problem gestoßen: Ich habe eine Funktion wie

someFunc :: Int -> Int -> Int -> Int

Was ich gerne machen würde, ist eine magische Funktion zu erstellen, die zum Beispiel

listApply :: [Int] -> (Int -> Int -> Int -> Int) -> Int

so dass ich sagen kann

listApply [1,2,3] someFunc

Instinktiv scheint es, und Johns Antwort stimmt dem zu, dass es möglich sein sollte, eine Art Systemmagie einzusetzen, um dies zu erreichen. Es gibt Lösungen für ähnliche Probleme, die explizit iso-rekursive Datentypen mit einem Haufen expliziter Rolls und Unrolls beinhalten (siehe z.B. Kapitel 20 von Types and Programming Languages, oder den vierten Beitrag in dieses Thema ).

Ich habe eine Weile an der Type-Lösung herumgehackt; es fühlt sich möglich an, aber ich habe es nicht ganz hinbekommen, bevor ich mich entschied, Template Haskell auszuprobieren, und dort sind die Dinge viel freundlicher.

{-# LANGUAGE TemplateHaskell #-}    

import Language.Haskell.TH
import Language.Haskell.TH.Syntax

lApply :: [Int] -> String -> ExpQ
lApply    []  fn = return $ VarE (mkName fn)
lApply (l:ls) fn = [| $(lApply ls fn) l |]

(Denken Sie daran, das Pragma LANGUAGE oder den Befehlszeilenschalter -XTemplateHaskell zu verwenden).

Um dies zu verwenden, rufen Sie lApply innerhalb eines Splice wie folgt auf:

> $(lApply [1,2] "+")
3

Beachten Sie, dass ich eine Zeichenkette verwenden muss, die den Namen der Funktion enthält, die ich aufrufen möchte: Ich kann eine Funktion nicht direkt in eine ExpQ heben, aber ich kann auf ihre globale Bindung verweisen. Ich kann mir vorstellen, dass dies lästig werden könnte. Außerdem müssen die Argumente aufgrund der Art und Weise, wie wir die Liste durchlaufen, in umgekehrter Reihenfolge in der Liste dargestellt werden.

Es gibt noch ein paar andere Probleme: Um dies auf andere Datentypen zu verallgemeinern, müssen diese Typen entsprechende Instanzen in der Klasse Lift haben. Double hat zum Beispiel keine Instanz, aber man kann leicht eine erstellen:

instance Lift Double where
        lift x = return $ LitE (RationalL (toRational x))

Der Lit-Datentyp hat keinen DoubleL-Konstruktor, aber RationalL kann an seiner Stelle verwendet werden, da er auf ein allgemeines Mitglied der Klasse Fractional spleißt.

Wenn Sie dies mit Funktionen verwenden wollen, die eine Mischung von Typen als Argumente annehmen, können Sie keine Liste übergeben, da Listen nicht von gemischten Typen sein können. Sie könnten dafür Tupel verwenden, was mit Template Haskell auch nicht viel schwieriger ist. In diesem Fall würden Sie eine Funktion erstellen, die den AST einer Funktion generiert, die ein Tupel mit den entsprechenden Typen darin nimmt und es auf den gewünschten Funktionsaufruf abbildet. Alternativ könnten Sie Ihre Argumenttypen in eine entsprechend gestaltete ADT verpacken, die Sie übrigens auch mit Template Haskell erstellen könnten. Dies sei dem Leser als Übung überlassen :)

Schließlich gelten alle Standardbeschränkungen von Template Haskell. Zum Beispiel können Sie diese Funktion aufgrund der GHC-Stage-Beschränkung nicht aus dem Modul aufrufen, in dem sie definiert ist.

Template Haskell ist lustig und interessant, aber um ganz ehrlich zu sein, ist die iso-rekursive Datentyplösung wahrscheinlich ein bisschen leistungsfähiger und erfordert offensichtlich nicht die zusätzliche Verwendung von TH. Ich werde zurückkommen und eine Fortsetzung posten, wenn ich das zum Laufen bringe :)

2voto

Landei Punkte 53286

Sie können nicht einmal die Fälle für verschiedene Längen "manuell" auflisten:

mf f [] = f
mf f [x] = f x
mf f [x,y] = f x y

--Occurs check: cannot construct the infinite type: t = t1 -> t
--Probable cause: `f' is applied to too many arguments
--In the expression: f x
--In the definition of `mf': mf f [x] = f x

Das bedeutet, dass mf keine Funktion von beliebiger "Arität" annehmen kann, sondern dass man sich für eine entscheiden muss. Aus dem gleichen Grund kann man eine beliebige Liste nicht in ein Tupel umwandeln: Man kann nicht sagen, wie viele Elemente das Tupel enthalten wird, aber das Typsystem muss es wissen.

Es könnte eine Lösung geben, indem man f auf einen rekursiven Typ a = a -> a beschränkt, indem man "forall" verwendet (siehe http://www2.tcs.ifi.lmu.de/~abel/fomega/hm.html ). Allerdings konnte ich nicht bekommen es funktioniert (es scheint, ich habe zu sagen, Leksah zu verwenden, die Flagge -XRankNTypes irgendwo), und diese Einschränkung auf f würde Ihre Funktion ziemlich nutzlos machen.

[Bearbeiten]

Wenn man darüber nachdenkt, kommt man dem, was man will, wahrscheinlich am nächsten, wenn man eine Art von Reduktionsfunktion hat. Wie Paul schon sagte, ist dies ähnlich wie foldl, foldr... (aber die untenstehende Version ist ohne "extra Argument" und mit einem homogenen Typ im Vergleich zu foldl. Beachten Sie den fehlenden Basisfall für leere Listen)

mf :: (a  a  a) -> [a] -> a
mf f [x] = x
mf f (x:y:xs) = mf f ((f x y) : xs)

1voto

user268396 Punkte 10938

Ich denke, das wird nicht funktionieren. (((quadsum 4) 3) 2) hat einen anderen Typ Intger -> Integer zum Beispiel als ((((quadsum 4) 3) 2) 1) die einen Typ von Integer .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X