#list #function #haskell
#Список #функция #haskell
Вопрос:
Я начну с того, что я новичок в Haskell, поэтому я еще не узнал о таких вещах, как монады.
В Haskell я пытаюсь создать тип дерева, листья которого имеют числа, а функции — ветви, чтобы все дерево могло действовать как калькулятор.
Вот мой код на данный момент. В настоящее время вместо того, чтобы использовать функции в качестве входных данных, я просто использую символы.
data Tree3 = Leaf3 Int | Node3 Char Tree3 Tree3 deriving (Show)
-- I would like to replace this ^ Char somehow with a function.
evaluate :: Tree3 -> Int
evaluate (Leaf3 x) = x
evaluate (Node3 c m n) | c == ' ' = evaluate m evaluate n
| c == '-' = evaluate m - evaluate n
| c == '/' = evaluate m `div` evaluate n
| c == '*' = evaluate m * evaluate n
Итак, мой вопрос в том, могу ли я ввести функцию в структуру данных (и каков будет тип?)
Извините за, возможно, запутанный вопрос, но спасибо за любой совет!
Ответ №1:
Я бы рекомендовал написать ваше дерево как:
data Tree = Leaf Int | Node (Int -> Int -> Int) Tree Tree
Обратите внимание, что вы не сможете вывести Eq
or Show
, поскольку Int -> Int
не реализует ни один из этих классов типов (и это невозможно непрактично это делать).
Затем вы можете написать свою evaluate
функцию как
evaluate :: Tree -> Int
evaluate (Leaf x) = x
evaluate (Node f l r) = f (evaluate l) (evaluate r)
что намного проще!
Вы можете создать дерево для представления выражения типа (1 2) * (3 * 4)
в виде
expr :: Tree
expr = Node (*) (Node ( ) (Leaf 1) (Leaf 2)) (Node (*) (Leaf 3) (Leaf 4))
Другим способом, который упростил бы печать вашего дерева, было бы использование почти того же определения, которое у вас есть:
data Tree = Leaf Int | Node String Tree Tree
-- ^ String instead of Char
Затем, если вы Data.Map
импортировали, вы можете создать карту функций для поиска, но это немного усложняет вашу evaluate
функцию, поскольку вы вводите вероятность того, что вашей функции не будет на вашей карте. К счастью, в Haskell есть несколько действительно удобных инструментов для элегантной обработки этого!
import qualified Data.Map as Map
type Tree = Leaf Int | Node String Tree Tree deriving (Eq, Show)
type FuncMap = Map.Map String (Int -> Int -> Int)
evaluate :: FuncMap -> Tree -> Maybe Tree
evaluate funcs (Leaf x) = return x
evaluate funcs (Node funcName left right) = do
-- Use qualified import since there's a Prelude.lookup
f <- Map.lookup funcName funcs
l <- evaluate funcs left
r <- evaluate funcs right
return $ f l r
Это автоматически приведет к Nothing
, если вы попробуете что-то вроде
evaluate (Map.fromList [(" ", ( ))]) (Node "blah" (Leaf 1) (Leaf 2))
поскольку функции "blah"
нет в вашем FuncMap
. Обратите внимание, что нам не пришлось выполнять какую-либо явную обработку ошибок любого рода благодаря Maybe
экземпляру monad! Если какой-либо из поисковых запросов к карте функций возвращает результат Nothing
, возвращается все вычисление Nothing
без необходимости думать об этом.
Комментарии:
1.
Eq
иShow
экземпляры дляInt -> Int -> Int
не являются невозможными (обратите внимание наa -> b
экземпляры для этих классов), просто непрактичны.2. @DanielWagner Да, вы действительно можете создавать экземпляры, но на практике от них мало пользы. Большинство людей хотели бы что-то вроде
Eq (Int -> Int -> Int)
, чтобы они могли делать что-то вродеif ( ) == ( ) then ... else ...
, но это просто не работает в Haskell. Хотя это и несколько неточно, я хотел увести читателей от мысли, что они могут сравнивать функции в том, что я бы назвал «человеческим» чувством равенства. Действительно, экземпляр может быть определен какinstance Eq (a -> b) where _ == _ = True
или альтернативно сFalse
, но это все еще не имеет практического применения.3. Экземпляр, приведенный в моей ссылке, на самом деле корректно сравнивает на предмет семантического равенства, а не просто
_ == _ = True
. Но я свободно признаю, что, несмотря на его правильность, это все еще непрактично — сравнение( ) == ( )
вызвало бы(==) :: Int -> Int -> Int
около 2 ^ 128 раз на 64-разрядной машине!4. Но примечательно, что все, что имеет небольшой домен, может быть довольно эффективно проверено. Это своего рода сногсшибательный момент, когда вы впервые понимаете, что
Bool -> a
это(a, a)
. Морально.5. Используйте оболочку:
newtype ShownAs a = ShownAs a String; use (ShownAs a _) = a; instance Show (ShownAs a) where show (ShownAs _ s) = s;
Тогда ваш более крупный тип все еще может использоватьсяderiving
.