9

Jestem ciekawa, czy można napisać funkcję apply_nth, która przyjmuje funkcję, numer parametru i wartość tego parametru, a następnie zwraca nową, częściowo zastosowaną funkcję.Czy można częściowo zastosować n-tego parametru w Haskell?

Mam wrażenie, że jest to niemożliwe ze względu na system typów, ale nie mogę wymyślić satysfakcjonującej odpowiedzi. Nie mogę również wymyślić podpisu roboczego.

Jeśli język był luźniejszy, wyobrażam sobie, że kod może wyglądać tak.

apply_nth f 0 x = f x 
apply_nth f n x = \a -> apply_nth (f a) (n-1) x 

Wszelkie pomysły?

+0

Wyszukiwanie "funkcji variadycznych w Haskell" – Bergi

+0

[W szczególności jest] (http://okmij.org/ftp/Haskell/polyvariadic.html#polyvar-fn) –

+0

Sądzę, że mogłem [coś powiedzieć o tym problemie, dawno temu] (http://strictlypositive.org/ faking.ps.gz). Niektórzy wciąż mogą zaprzeczać, ale Haskell w rzeczywistości był naprawdę dość zależny od pisania naprawdę przez jakiś czas. – pigworker

Odpowiedz

7

Twoje odczucia są poprawne, nie jest to możliwe. Częściowa aplikacja zmienia typ funkcji iw zależności od tego, jaki parametr zastosujesz. Ale jeśli ten parametr jest indeksowany tylko w środowisku wykonawczym z dodatkowym argumentem, kompilator nie wie, jaki będzie typ, a kompilator musi sprawdzić typowo wszystko: & dagger;. Naprawdę, potrzebujesz wyniku, aby uzyskać dependent type, ale Haskell nie jest językiem zależnym.

Teraz, jeśli wrzucisz kilka rozszerzeń GHC i wprowadzisz kilka dziwnych rodzin typów, możesz faktycznie osiągnąć coś podobnego do takiego typu zależnego. Ale szczerze mówiąc, wątpię, żeby to był dobry pomysł. Czego potrzebujesz mimo to? Jeśli żonglujesz funkcjami z więcej niż, powiedzmy, 8 parametrami, prawdopodobnie robisz coś źle, a dla łatwiejszych funkcji możesz zdefiniować 8 kombinatorów, z których każdy stosuje pojedynczą, ustaloną pozycję argumentu.

Alternatywnie: podobna funkcja, która jest być może uzasadnione byłoby

apply_nth :: ([a] -> b) -> Int -> a -> [a] -> b 
apply_nth f i a xs = f $ before ++ [a] ++ after 
where (before, after) = splitAt i xs 

przeciwieństwie do argumentów listach wartość-lista może łatwo być setki elementów długich, więc w tym przypadku wstępnie stosowania pojedynczych elementów, indeksowane w czasie wykonywania może mieć sens.


& sztylet; To nie jest tylko środek ostrożności - jest to konieczne, ponieważ typy nie istnieją nawet w środowisku wykonawczym, więc kompilator musi ukończyć przygotowania wszystkich warunków, które mogą zależeć od typów. Właśnie dlatego Haskell jest bezpieczny, tak jak kilka innych języków.

+0

To nie jest częścią większego projektu ani nic. Myślałem o rachunku Lambda i traktowaniu funkcji jako danych i zastanawiałem się, czy można to zrobić. Dziękuję za Twoją odpowiedź! – danmcardle

4

Jasne, z odrobiną klasy rodzaju "magii":

{-# LANGUAGE DataKinds, KindSignatures, UndecidableInstances #-} 

data Nat = Z | S Nat 

data SNat (n :: Nat) where 
    SZ :: SNat Z 
    SS :: SNat n -> SNat (S n) 

class ApplyNth (n :: Nat) arg fn fn' | n arg fn -> fn', n fn -> arg where 
    applyNth :: SNat n -> arg -> fn -> fn' 

instance ApplyNth Z a (a -> b) b where 
    applyNth SZ a f = f a 

instance ApplyNth n arg' fn fn' => ApplyNth (S n) arg' (arg0 -> fn) (arg0 -> fn') where 
    applyNth (SS n) a f = \a0 -> applyNth n a (f a0) 

ogólnego typu dla applyNth mówi, zajmuje indeks (liczbę naturalną - zakodowany w typie), argument, A funkcja i zwraca funkcję.

Zwróć uwagę na dwie zależności funkcjonalne. Pierwsza mówi, że biorąc pod uwagę indeks, argument i funkcję wejściową, znany jest typ funkcji wyjściowej. To jest oczywiste. Drugi mówi, że biorąc pod uwagę indeks i funkcję wejściową, ApplyNth jest w stanie zajrzeć do funkcji i dowiedzieć się, jakiego argumentu potrzebuje!

Funkcja ta gra całkiem dobrze z typu wnioskowania:

>:t \x -> applyNth (SS SZ) x (^) 
\x -> applyNth (SS SZ) x (^) 
    :: (Num fn', Integral b) => b -> fn' -> fn' 
>:t applyNth (SS SZ) 0 (^) 
applyNth (SS SZ) 0 (^) :: Num fn' => fn' -> fn' 
>:t applyNth (SS SZ) (0 :: Integer) (^) 
applyNth (SS SZ) (0 :: Integer) (^) :: Num fn' => fn' -> fn' 
>:t applyNth (SS SZ) ('a' :: Char) (^) 

<interactive>:1:32: Warning: 
    Could not deduce (Integral Char) arising from a use of `^' 
    ... 
applyNth (SS SZ) ('a' :: Char) (^) :: Num fn' => fn' -> fn' 
>let squared = applyNth (SS SZ) 2 (^) 
>:t squared 
squared :: Num fn' => fn' -> fn' 
>squared 3 
9 
>squared 100 
10000 
>let f a b c d e = mapM_ putStrLn 
     [ show n ++ ": " ++ x 
     | (n,x) <- zip [0..] 
      [show a, show b, show c, show d, show e] ] 
>applyNth SZ 'q' $ 
applyNth (SS $ SZ) [1,8,42] $ 
applyNth SZ (True, 10) $ 
applyNth (SS $ SS $ SS SZ) "abcd" $ 
applyNth (SS $ SS $ SS SZ) pi $ 
f 
0: (True,10) 
1: 'q' 
2: [1,8,42] 
3: 3.141592653589793 
4: "abcd" 

Można również zdefiniować go w postaci operatora:

infixl 9 =: 
(=:) :: ApplyNth n arg fn fn' => SNat n -> arg -> fn -> fn' 
(=:) = applyNth 

r = 
SZ =: 'q' $ 
SS SZ =: [1,8,42] $ 
SZ =: (True, 10) $ 
(SS $ SS $ SS SZ) =: "abcd" $ 
(SS $ SS $ SS SZ) =: pi $ 
f 
1

nie wewnątrz każdego języka zwanego „Haskell”, ale jeśli spojrzeć na Glasgow Haskell, w tym niebezpieczne funkcje, można częściowo zastosować w sposób, w jaki sobie tego życzysz ... cóż, musisz poprawnie określić lokalizację argumentu. TO JEST STRACONY HACK. Nie rób tego, chyba że czujesz się wyjątkowo dobrze z ... cóż ... nie rób tego.

Ten kod jest z powrotem, gdy zadałem podobne pytanie (Using Typeable to partially apply function at run-time (any time types match)).

import Unsafe.Coerce 

testBuild :: String 
testBuild = let f = buildFunc typedFunction ("argument 'b'", 42::Int) 1 
      in f "Look I have applied " " and it seems to work." 

typedFunction :: String -> (String,Int) -> String -> String 
typedFunction = (\a b c -> a ++ show b ++ c) 

buildFunc :: f -> x -> Int -> g 
buildFunc f x 0 = unsafeCoerce f x 
buildFunc f x i = 
     let res = \y -> (buildFunc (unsafeCoerce f y) x (i-1)) 
     in unsafeCoerce res 

a wyjście:

*Main> testBuild 
"Look I have applied (\"argument 'b'\",42) and it seems to work." 

Wskazówka jeśli określony indeks argument (1) nieprawidłowo następnie program prawdopodobnie się wysypać.

+1

Jak mówi: Nie rób tego! – augustss

+0

Tak, na pewno warto powtórzyć. –

8

Nie takie dziwne rodziny typu, ale nie super miły albo:

{-# LANGUAGE GADTs, DataKinds, TypeFamilies, TypeOperators #-} 

import Data.Proxy 

type family Fun as b where 
    Fun '[]  b = b 
    Fun (a ': as) b = a -> Fun as b 

data SL as where 
    Sn :: SL '[] 
    Sc :: SL as -> SL (a ': as) 

applyN :: Proxy c -> SL as -> Fun as (b -> c) -> b -> Fun as c 
applyN p Sn f y = f y 
applyN p (Sc s) f y = \x -> applyN p s (f x) y 

main = print $ applyN Proxy (Sc (Sc Sn)) zipWith [1,2,3] (-) [6,5,4] -- [5,3,1] 

Możemy również zapakować Proxy c do SL:

data SL as c where 
    Sn :: SL '[] c 
    Sc :: SL as c -> SL (a ': as) c 

applyN :: SL as c -> Fun as (b -> c) -> b -> Fun as c 
applyN Sn f y = f y 
applyN (Sc s) f y = \x -> applyN s (f x) y 

main = print $ applyN (Sc (Sc Sn)) zipWith [1,2,3] (-) [6,5,4] -- [5,3,1] 

Albo można po prostu zdefiniować kilka kombinatorów:

z = id 
s r f y x = r (f x) y 
applyN = id 

main = print $ applyN (s (s z)) zipWith [1,2,3] (-) [6,5,4] -- [5,3,1]