Em um problema de aprendizado com o qual estou brincando, percebi que precisava de uma classe para funções com operações para aplicação, composição etc. Razões ...
Pode ser conveniente tratar uma representação de uma função como se fosse a própria função, de modo que a aplicação implícita da função use um intérprete e a composição de funções deriva uma nova descrição.
Depois de ter uma classe para funções, você pode derivar classes para tipos especiais de funções - no meu caso, eu quero funções invertíveis.
Por exemplo, funções que aplicam deslocamentos de número inteiro podem ser representadas por um ADT contendo um número inteiro. Aplicar essas funções significa apenas adicionar o número inteiro. A composição é implementada adicionando os números inteiros quebrados. A função inversa tem o número inteiro negado. A função de identidade quebra zero. A função constante não pode ser fornecida porque não há representação adequada para ela.
É claro que não é necessário escrever as coisas como se os valores fossem funções genuínas de Haskell, mas depois que tive a ideia, pensei que uma biblioteca como essa já deveria existir e talvez até usar as grafias padrão. Mas não consigo encontrar essa classe na biblioteca Haskell.
Encontrei o módulo Data.Function , mas não há classe - apenas algumas funções comuns que também estão disponíveis no Prelude.
Então - por que não há uma classe para funções? É "apenas porque não existe" ou "porque não é tão útil quanto você pensa"? Ou talvez haja um problema fundamental com a ideia?
O maior problema possível que eu pensei até agora é que a aplicação de funções em funções reais provavelmente teria que ser especificada pelo compilador para evitar um problema de loop - para aplicar esta função, preciso aplicar a função de aplicação de funções, e para fazer isso eu preciso chamar a função application application, e para fazer isso ...
Mais pistas
Exemplo de código para mostrar o que estou buscando ...
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
-- In my first version, Doable only had the one argument f. This version
-- seemed to be needed to support the UndoableOffset type.
--
-- It seems to work, but it also seems strange. In particular,
-- the composition function - a and b are in the class, but c isn't,
-- yet there's nothing special about c compared with a and b.
class Doable f a b where
fwdApply :: f a b -> a -> b
compDoable :: f b c -> f a b -> f a c
-- In the first version, I only needed a constraint for
-- Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
bwd :: f a b -> f b a
bwdApply :: f a b -> b -> a
bwdApply f b = fwdApply (bwd f) b
-- Original ADT - just making sure I could wrap a pair of functions
-- and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }
instance Doable UndoableFn a b where
fwdApply = getFwd
compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))
instance Undoable UndoableFn a b where
bwd f = UFN (getBwd f) (getFwd f)
bwdApply = getBwd
-- Making this one work led to all the extensions. This representation
-- can only represent certain functions. I seem to need the typeclass
-- arguments, but also to need to restrict which cases can happen, hence
-- the GADT. A GADT with only one constructor still seems odd. Perhaps
-- surprisingly, this type isn't just a toy (except that the whole thing's
-- a toy really) - it's one real case I need for the exercise. Still a
-- simple special case though.
data UndoableOffset a b where
UOFF :: Int -> UndoableOffset Int Int
instance Doable UndoableOffset Int Int where
fwdApply (UOFF x) y = y+x
compDoable (UOFF x) (UOFF y) = UOFF (x+y)
instance Undoable UndoableOffset Int Int where
bwdApply (UOFF x) y = y-x
bwd (UOFF x) = UOFF (-x)
-- Some value-constructing functions
-- (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)
undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)
-- With UndoableFn, it's possible to define an invertible function
-- that isn't invertible - to break the laws. To prevent that, need
-- the UFN constructor to be private (and all public ops to preserve
-- the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x
-- Validating a multiply-by-zero invertible function shows the flaw
-- in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
putStrLn . show $ validate (undoableMul 3) 5
--putStrLn . show $ validate (undoableMul 0) 5
fb1 <- return $ UOFF 5
fb2 <- return $ UOFF 7
fb3 <- return $ compDoable fb1 fb2
putStrLn $ "fwdApply fb1 3 = " ++ (show $ fwdApply fb1 3)
putStrLn $ "bwdApply fb1 8 = " ++ (show $ bwdApply fb1 8)
putStrLn $ "fwdApply fb3 2 = " ++ (show $ fwdApply fb3 2)
putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)
O aplicativo envolve um tipo de unificação em que os valores unificados não são iguais, mas estão relacionados por meio de funções invertíveis - lógica no estilo Prolog, mas com a = f(b)
restrições em vez de a = b
. A maior parte da composição resultará da otimização de uma estrutura de busca de união. A necessidade de inversos deve ser óbvia.
Se nenhum item em um conjunto unificado tiver um valor exato, um item específico poderá ser quantificado apenas em relação a outro item nesse conjunto unificado. É por isso que não quero usar funções "reais" - calculando esses valores relativos. Eu poderia abandonar todo o aspecto da função e apenas ter quantidades absolutas e relativas - provavelmente só preciso de números / vetores e (+)
- mas meu astronauta de arquitetura interna quer a diversão dele.
A única maneira de separar os links novamente é através do backtracking, e tudo é puro - a busca por união será feita usando as chaves em IntMap
"ponteiros". Eu tenho um trabalho simples de encontrar união, mas como ainda não adicionei as funções invertíveis, não faz sentido listá-lo aqui.
Razões pelas quais não posso usar o aplicativo, mônada, seta etc.
As principais operações que eu preciso que a classe de abstração da função forneça são aplicação e composição. Isso parece familiar - por exemplo Applicative
(<*>)
, Monad
(>>=)
e Arrow
(>>>)
são todas funções de composição. No entanto, os tipos que implementam a abstração da função no meu caso conterão alguma estrutura de dados que representa uma função, mas que não é (e não pode conter) uma função e que pode representar apenas um conjunto limitado de funções.
Como mencionei na explicação do código, às vezes só posso quantificar um item em relação a outro porque nenhum item em um cluster "unificado" tem um valor exato. Eu quero ser capaz de derivar uma representação dessa função, que em geral será a composição de várias funções fornecidas (subindo para um ancestral comum na árvore união / localização) e de várias funções inversas (voltando para a outra item).
Caso simples - onde as "funções" originais são limitadas às "funções" de deslocamento inteiro, quero o resultado composto como uma "função" de deslocamento inteiro - adicione as compensações do componente. Essa é uma parte importante do motivo pelo qual a função de composição precisa estar na classe e também na função de aplicativo.
Isso significa que não posso fornecer as operações pure
, return
ou arr
para os meus tipos, então não posso usar Applicative
, Monad
ou Arrow
.
Não é uma falha desses tipos - é uma incompatibilidade de abstrações. A abstração que eu quero é de uma simples função pura. Não há efeito colateral, por exemplo, e não é necessário criar uma notação conveniente para sequenciar e compor as funções que não sejam equivalentes ao padrão (.) Que se aplica a todas as funções.
Eu poderia instância Category
. Estou confiante de que todas as minhas coisas funcionais serão capazes de fornecer uma identidade, embora eu provavelmente não precise dela. Mas como Category
ele não suporta aplicativos, ainda assim eu precisaria de uma classe derivada para adicionar essa operação.
fonte
Applicative
esteja certo - ele exige que os valores sejam agrupados, assim como as funções, enquanto eu só quero agrupar as funções, e as funções agrupadas são realmente funções, enquanto minhas funções agrupadas normalmente não serão (em o caso mais geral, são ASTs que descrevem funções). Onde<*>
tem tipof (a -> b) -> f a -> f b
, quero um operador de aplicativo com o tipog a b -> a -> b
ondea
eb
especifique o domínio e o código da função agrupada, mas o que está dentro do wrapper não é (necessariamente) uma função real. No Arrows - possivelmente, vou dar uma olhada.Respostas:
Bem, não conheço nenhuma ideia assada que se comercialize como representando coisas "funcionais e". Mas há vários que se aproximam
Categorias
Se você tem um conceito de função simples que possui identidades e composição, então você tem uma categoria.
A desvantagem é que você não pode criar uma instância de categoria legal com um conjunto de objetos (
a
,b
, ec
). Você pode criar uma classe de categoria personalizada, suponho.Setas; flechas
Se suas funções tiverem noção de produtos e puderem injetar funções arbitrárias, as setas são para você
ArrowApply
tem uma noção de aplicação que parece importante para o que você deseja.Requerentes
Os candidatos têm sua noção de aplicação, eu os usei em um AST para representar aplicativo de função.
Existem muitas outras idéias. Mas um tema comum é criar alguma estrutura de dados representando sua função e passá-la para uma função de interpretação.
Isso também funciona com quantas mônadas gratuitas. Eu sugiro cutucar neles se você estiver se sentindo corajoso, eles são uma ferramenta poderosa para as coisas que você está sugerindo e, essencialmente, permitem que você construa uma estrutura de dados usando a
do
notação e depois a recolha em uma computação de efeito colateral com funções diferentes . Mas o mais interessante é que essas funções funcionam apenas na estrutura de dados e não estão realmente conscientes de como você fez tudo isso. Isso é o que eu sugeriria para o seu exemplo de intérprete.fonte
($)
. As setas parecem um enorme exagero à primeira vista, mas aindaArrowApply
parecem promissoras - desde que eu não precise fornecer nada que não possa, pode estar tudo bem. +1 no momento, com mais verificação a fazer.Applicative
ouArrow
(ouMonad
) - não consigo agrupar uma função normal (em geral) porque os valores do meu tipo representam uma função, mas são representados por dados e não suportam funções arbitrárias, se havia uma maneira de traduzir. Isso significa que não posso fornecerpure
,arr
oureturn
por exemplo. BTW - essas classes são úteis, mas não posso usá-las para esse fim específico.Arrow
não é um "exagero maciço" - foi uma falsa impressão desde a última vez que tentei ler o jornal, quando não estava pronto para entendê-lo.Como você ressalta, o principal problema com o uso do Applicative aqui é que não há uma definição sensata para
pure
. Por isso,Apply
foi inventado. Pelo menos, esse é o meu entendimento.Infelizmente, não tenho exemplos disponíveis de instâncias
Apply
que também não sãoApplicative
. Alega-se que isso é verdadeIntMap
, mas não tenho idéia do porquê. Da mesma forma, não sei se o seu exemplo - deslocamento inteiro - admite umaApply
instância.fonte
Além do referido
Category
,Arrow
eApplicative
:Eu também descobri
Data.Lambda
por Conal Elliott:Parece interessante, é claro, mas difícil de entender sem exemplos ...
Exemplos
Exemplos podem ser encontrados na página wiki sobre valores tangíveis (TV) que parecem ser uma das coisas que causaram a criação da
TypeCompose
biblioteca; consulte Entradas e saídas com valor de função .A idéia da biblioteca de TV é exibir valores Haskell (incluindo funções) de maneira tangível.
Para seguir a regra StackOverflow sobre não postar vazios, copio alguns bits abaixo, que devem dar a idéia do seguinte:
O primeiro exemplo diz:
que fornece quando executado como
runIO shopping
(veja mais comentários, GUIs e mais exemplos):fonte
Data.Lambda
dar classes para coisas como funções (que foi solicitado) ... eu não tinha certeza de como essas coisas devem ser usadas. Eu explorei isso um pouco. Provavelmente, eles não fornecem uma abstração para a aplicação de funções.