WebHU - Programozási kérdések és válaszok

Hogyan lehet egy függvényt transzparensen bővíteni a Haskellben?

Helyzet

Van egy f függvényem, amelyet ki szeretnék egészíteni a g függvénnyel, ami a h nevű függvényt eredményezi.

Definíciók

A "kiegészítés" alatt az általános esetben azt értem, hogy a f függvény bemenetét (egy vagy több argumentum) vagy kimenetét (visszatérési érték) átalakítja.

A "kiegészítés" alatt specifikus esetben (jelenlegi helyzetemre jellemző) azt értem, hogy az f függvénynek csak a kimenetét (visszatérési értékét) alakítsa át, miközben az összes argumentumot érintetlenül hagyja.

Az "átlátszó" alatt a "kiegészítés" kontextusában (mind az általános eset, mind a specifikus eset) azt értem, hogy a g implementációját olyan lazán kapcsoljuk össze a f megvalósításával, mint lehetséges.

Konkrét eset

Jelenlegi helyzetemben a következőket kell tennem:

h a b c = g $ f a b c

Érdekelne, hogy átírjam valami ilyesmire:

h = g . f -- Doesn't type-check.

Mivel h és g szemszögéből nézve mindegy, hogy f milyen érveket vesz fel, csak a visszatérési értékkel foglalkoznak, ezért szoros párosítás lenne az érveket bármilyen módon megemlíteni. Például, ha a f argumentumszáma a jövőben megváltozik, akkor a h értéket is módosítani kell.

Eddig

Kérdeztem a lambdabot a #haskell IRC csatornán: @pl h a b c = g $ f a b c, amire azt a választ kaptam:

h = ((g .) .) . f

Ami még mindig nem elég jó, mivel a (.)-ek száma f argumentumainak számától függ.

Általános eset

Nem sokat kutattam ebben az irányban, de erisco a #haskell oldalon a http://matt.immute.net/content/pointless-fun, ami azt sugallja számomra, hogy lehetséges megoldás az általános esetre.

Eddig

A Luke Palmer által a fenti cikkben definiált függvényeket használva ez egyenértékűnek tűnik azzal, amit eddig tárgyaltunk:

h = f $. id ~> id ~> id ~> g

Úgy tűnik azonban, hogy ez a metódus is sajnos a f argumentumainak számától függ, ha a f visszatérési értékét szeretnénk átalakítani -- akárcsak az előző módszerek.

Működő példa

A JavaScriptben például lehetséges az ilyen átlátszó kiegészítés:

function h () { return g(f.apply(this, arguments)) }

Kérdés

Hogyan lehet egy függvényt "átláthatóan bővíteni" a Haskellben?

Főleg a specifikus eset érdekel, de jó lenne tudni, hogyan kell kezelni az általános esetet.

16.04.2014

  • Tegyük fel, hogy lehetséges, akkor mi legyen az h típusa? Ez nem a f típusától függ? 16.04.2014
  • @LeeDuhem Igen, a h típusa alapján f lesz, kivéve a visszatérési értéket, amely a g visszatérési értékének típusa lesz. 16.04.2014
  • @LeeDuhem tetszik: f :: ... -> a {- we don't care/don't know what is in '...' -}, g :: a -> b és h :: ... -> b {- '...' is the same as for f -} 16.04.2014
  • h szüksége van egy típusra, hogy ... legyen valami. Bár a típusváltozóval ki lehetne fejezni azt a jelentést, hogy nem érdekel, mi az adott típus, a Haskellben tudomásom szerint nincs mód annak kifejezésére, hogy nem érdekel, hány paraméter van. 16.04.2014
  • Ha explicitté teszed az implcit zárójeleket egy többargumentumos függvénytípusban, majd megpróbálod alkalmazni az ellipszis jelölést, látni fogod, miért olyan nehéz. (Az ellipszis nem fogja meg a jobboldali zárójeleket.) Pl. a -> b -> c -> d ~ a -> (b -> (c -> d)). 07.08.2014

Válaszok:


1

Nos, technikailag épp elég IncoherentInstances segítségével szinte bármit megtehet:

{-# LANGUAGE MultiParamTypeClasses, TypeFamilies,
  FlexibleInstances, UndecidableInstances, IncoherentInstances #-}

class Augment a b f h where
   augment :: (a -> b) -> f -> h

instance (a ~ c, h ~ b) => Augment a b c h where
   augment = ($)

instance (Augment a b d h', h ~ (c -> h')) => Augment a b (c -> d) h where
   augment g f = augment g . f

-- Usage
t1 = augment not not
r1 = t1 True

t2 = augment (+1) (+)
r2 = t2 2 3

t3 = augment (+1) foldr
r3 = t3 (+) 0 [2,3]
17.04.2014
  • Ha az osztályt és a két példányt a saját moduljába helyeznénk, és csak a augment függvényt exportálnánk, biztonságos lenne importálni és más modulokban használni? A biztonságos alatt azt értem: megbízható lenne, vagy furcsa és váratlan sarok esetei lennének? Nagyon szeretem, hogy így nem kell úgy felsorolni a típusokat, mint @hammar válaszánál, de kicsit óvatosan támaszkodok a névben inkoherens nyelvi pragmára. Rövid kérdés: ez egy szivárgó absztrakció? 17.04.2014
  • @Wizek Személy szerint nem értem, miért lenne "szivárgó", ha csak a augment függvényt exportálják. 17.04.2014
  • Nem lenne elég és egy kicsit biztonságosabb a OverlappingInstances pragma használata, ahogy @monocell rámutatott? 17.04.2014

  • 2

    Valahogy megteheti, de mivel nem lehet viselkedést megadni mindenre, ami nem függvény, sok triviális példányra lesz szüksége az összes többi fontos típushoz. ról ről.

    {-# LANGUAGE TypeFamilies, DefaultSignatures #-}
    
    class Augment a where
      type Result a
      type Result a = a
    
      type Augmented a r
      type Augmented a r = r
    
      augment :: (Result a -> r) -> a -> Augmented a r
    
      default augment :: (a -> r) -> a -> r
      augment g x = g x
    
    instance Augment b => Augment (a -> b) where
      type Result (a -> b) = Result b
      type Augmented (a -> b) r = a -> Augmented b r
    
      augment g f x = augment g (f x) 
    
    instance Augment Bool
    instance Augment Char
    instance Augment Integer
    instance Augment [a]
    
    -- and so on for every result type of every function you want to augment...
    

    Példa:

    > let g n x ys = replicate n x ++ ys
    > g 2 'a' "bc"
    "aabc"
    > let g' = augment length g
    > g' 2 'a' "bc"
    4
    > :t g
    g :: Int -> a -> [a] -> [a]
    > :t g'
    g' :: Int -> a -> [a] -> Int
    
    16.04.2014
  • @Wizek: Jó ötlet! Kész. 16.04.2014
  • Lehet-e zárt típusú családokat használni ennek további tisztítására a GHC 7.8-on? 16.04.2014
  • Hasonló kérdésem lenne hozzád, mint @Ed'ka: Ha az osztályt és a példányokat a saját moduljába helyeznénk, és csak az augment függvényt (és az Augment osztályt?) exportálnánk, biztonságos lenne az importálás és használja más modulokban? A biztonságos alatt azt értem: megbízható lenne, vagy furcsa és váratlan sarok esetei lennének? Tetszik, hogy nem az IncoherentInstances pragmára hagyatkozik, de természetesen van egy hátránya, hogy sok példányt meg kell adni. (Valószínűleg megtehetnénk úgy, ahogyan a Data.Default teszi...) Rövid kérdés: Ez egy szivárgó absztrakció? 17.04.2014
  • @Wizek: Megbízható, de lehet, hogy nem úgy működik, ahogy szeretnéd, ha a kiegészítendő függvény visszatérési típusa egy típusváltozó, mert ha ez a típusváltozó egy függvénytípusba kerül, akkor azt szeretné hogy tovább bővítse ezt. Alapvetően a bővítés addig nem áll le, amíg teljesen biztos nem lesz benne, hogy nem lehet több érv. Ez a legnagyobb különbség az ilyen módszer és a IncoherentInstances használata között, mivel ez bizonyos esetekben korán leállhat, ha az alapesetet választja a rekurzív függvénypéldány helyett. 17.04.2014
  • @Carl: Használhatnád ezt a típuscsaládokhoz, de akkor is kellenek a példányok, szóval nem hiszem, hogy ez sokat változna. 17.04.2014
  • @hammar Tudnál példát mondani a váratlan esetre? 17.04.2014
  • @Wizek: Például a augment (\f x -> f x x) (!!) [(||), (&&)] 0 True nem fog működni, mert megpróbálja folytatni a lista függvényeinek bővítését, de megteheti a augment not (!!) [(||), (&&)] 0 True False. Az első esetben a két argumentum megírása funkció működne, a második esetben a négy argumentum összeállítása funkció, de a augment soha nem tud kevesebbet írni, mint az összes, még akkor sem, ha a típusok azt mondanák, hogy ennek nincs értelme. . 17.04.2014
  • @Wizek: És az oka annak, hogy így kellett megvalósítanom, az a probléma, amibe bármilyen polimorf kompozíciós függvénynél bele fogsz ütközni: Nem mindig egyértelmű, hogy hány érvet kell összeállítani a múltban. 17.04.2014
  • @hammar Köszönöm szépen a választ. Ed'ka válaszát elfogadottként jelöltem meg, mivel úgy működik, hogy nem kell minden típushoz példányt megadni. És még ha össze is gyűjtöttünk egy listát, nem túl szép felület, ha megkérjük az augment függvény felhasználóit, hogy adjanak meg példányokat az általunk kihagyott típusokhoz. Ha Ön vagy (bárki más) még jobb alternatívával áll elő, tegye közzé, és újra hozzárendelem az elfogadott választ. :) 18.04.2014

  • 3

    A probléma az, hogy a a -> b -> c-hez hasonló dolgok valós visszatérési értéke nem c, hanem b -> c. Amit szeretne, valamilyen tesztre van szükség, amely megmondja, hogy egy típus nem függvénytípus. Felsorolhatnád azokat a típusokat, amelyek érdekelnek, de ez nem olyan szép. Szerintem HList oldja meg valahogy ezt a problémát, nézze meg a papírt. Sikerült egy kicsit megértenem a megoldást az átfedő esetekkel, de a többi kicsit átmegy a fejemen, attól tartok.

    16.04.2014
  • Nem lehet valahogy megmondani, hogy egy függvény függvény-e, ha nem fordítási időben, esetleg futási időben? 17.04.2014
  • Úgy gondolom, hogy ebben az esetben a várakozás semmit sem vásárolna. A típusellenőrzőt erre is lehet engedni, nézd meg Ed'ka, hammar válaszát vagy az általam linkelt papírt. Egyszerűen nem könnyű és nem szép. 17.04.2014
  • A megvalósításnak nem kell sem könnyűnek, sem szépnek lennie. Ha csúnya, de tökéletesen absztrahálható (hmm, mi a szivárgó absztrakció ellentéte?) úgy, hogy a saját moduljába teszem, akkor nem baj, ha onnan importálom a (megbízható) augment függvényt. 17.04.2014
  • Szerintem biztonságosnak kell lennie mindaddig, amíg nem határoz meg új példányokat. Azt is hiszem, hogy az OverlappingInstances elegendő, lehet, hogy nincs szüksége Inkoherensre. 17.04.2014

  • 4

    A JavaScript működik, mert argumentumai sorozatok vagy listák, tehát valójában csak egy argumentum van. Ebben az értelemben ez ugyanaz, mint a függvények curryed változata, amelyben az argumentumok gyűjteményét reprezentáló sor.

    Egy erősen begépelt nyelvben sokkal több információra van szükség ahhoz, hogy ezt "átláthatóan" megtegye egy függvénytípus esetében – például a függő típusok kifejezhetik ezt az elképzelést, de megkövetelik, hogy a függvények meghatározott típusúak legyenek, nem tetszőleges függvénytípusok.

    Azt hiszem, láttam egy megoldást a Haskellben, amely ezt is meg tudja tenni, de ismét csak bizonyos típusoknál működik, amelyek a függvény valódiságát rögzítik, nem bármely függvényt.

    16.04.2014
    Új anyagok

    A rádiógomb ellenőrzött eseményének használata a jQueryben
    Ebben a cikkben látni fogjuk, hogyan kell dolgozni a jquery választógombbal ellenőrzött eseményeivel. A választógombok HTML gombok, amelyek segítenek kiválasztani egyetlen értéket egy csoportból...

    Körkörös függőségek megoldása terraformban adatforrásokkal – lépésről lépésre
    Mi az a körkörös függőségek Dolgozzunk egy egyszerű eseten, amikor az SQS-sor és az S3-vödör közötti körkörös függőség problémája van egy egymástól függő címkeérték miatt. provider..

    Miért érdemes elkezdeni a kódolást 2023-ban?
    01100011 01101111 01100100 01100101 — beep boop beep boop Világunk folyamatosan fejlődik a technológia körül, és naponta fejlesztenek új technológiákat a valós problémák megoldására. Amint..

    🎙 Random Noise #2  – Örökbefogadás és hit
    az analitika íratlan világának gondozása Szeretné, hogy ezek a frissítések a postaládájába kerüljenek? Iratkozzon fel itt . "Ha önvezető autókat gyártanak, akkor mi miért ne..

    A legrosszabb politika és prediktív modellek májátültetésre jelöltek számára az Egyesült Államokban
    A máj (vagy óangolul lifer) az emberi test legnehezebb belső szervére utal, amely csendesen működik a nap 24 órájában. Mit csinál a máj? 500 feladatot hajt végre a szervezet egészségének..

    5 webhely, amely 2022-ben fejleszti front-end fejlesztői készségeit
    Frontendmentor.io A tényleges projektek létrehozásával a Frontendmentor.io segítséget nyújt a front-end kódolási képességeinek fejlesztésében. A kódolást azután kezdheti meg, hogy..

    Mikor kell használni a Type-t az interfészhez képest a TypeScriptben?
    A TypeScript a JavaScript gépelt szuperkészlete, amely statikus gépelést ad a nyelvhez. Ez megkönnyíti a robusztus és karbantartható kód írását azáltal, hogy a hibákat a fordítási időben..