Почему частичное приложение Haskell не работает?

Моя работа в Haskell заключается в переделке проектов .Net F# в Haskell для удовольствия.

Я разбираю обычный файл конфигурации Windows — одна пара ключ/значение в строке, ключ отделен от значения =. Этот файл очень прост и прямолинеен, что позволяет моему коду синтаксического анализа быть простым и прямолинейным, что мне нравится.

Мой вопрос в том, почему частичное приложение не работает в последней строке кода ниже. Очевидно, он работает в предыдущих строках и других функциях.

module Configuration (Config (..), load) where

import Data.Char (isSpace)
import Data.List (isPrefixOf)

data Config = Config { aliases :: [String]
                     , headers :: [String] }

-- This is a naive implementation. I can't decide if I like it better than
-- trim = unpack . strip . pack.
trim :: String -> String
trim = reverse . dropSpaces . reverse . dropSpaces

dropSpaces :: String -> String
dropSpaces = dropWhile isSpace

split _ [] = []
split c cs = [takeWhile (/= c) cs] ++ split c (tail' $ dropWhile (/= c) cs)
  where tail' []     = []
        tail' (x:xs) = xs

load :: String -> Config
load text =
  let ss = lines text
      hs = map getValue $ getLines "Header" ss
      as = split ',' $ getValue $ getLine "AliasList" ss
   in Config { aliases=as, headers=hs }
  where getLines p = filter (p `isPrefixOf`)
        getValue   = trim . drop 1 . dropWhile (/= '=')
        getLine    = head . getLines -- Why isn't partial application working here?

Ошибка, которую я получаю, следующая:

Configuration.hs:30:29:
    Couldn't match expected type `[c0]'
                with actual type `[[a0]] -> [[a0]]'
    Expected type: [a0] -> [c0]
      Actual type: [a0] -> [[a0]] -> [[a0]]
    In the second argument of `(.)', namely `getLines'
    In the expression: head . getLines

Спасибо!


person Jeff Maner    schedule 15.11.2013    source источник


Ответы (2)


Это не частичное применение, а композиция функций, которая не работает. Вы не можете передать 2 аргумента функции, которая является частью функциональной композиции.

person Ingo    schedule 15.11.2013
comment
Мне было интересно, было ли это чем-то настолько простым, как невозможность передать два аргумента функции, которая является частью функциональной композиции, но я не смог найти это с помощью простого поиска в Google. - person Jeff Maner; 15.11.2013

Первое, что нужно отметить, это подпись getLines. Поскольку p `isPrefixOf` имеет тип (Eq a) => [a] -> Bool, getLines p имеет тип (Eq a) => [[a]] -> [[a]] (на основе типа filter). Здесь [a] кажется String, поэтому getLines p имеет тип [String] -> [String], а p, таким образом, имеет тип String. Так что на самом деле getLines имеет тип String -> [String] -> [String].

Наконец, head имеет (специализированный) тип [String] -> String, и вы пытаетесь посткомпоновать его с помощью getLines. Я предполагаю, что вы пытаетесь создать функцию с типом String -> [String] -> String, определенным \p ss -> head (getLines p ss). Однако это не то, чем является head . getLines!

Чтобы увидеть это, рассмотрим f :: a -> b -> c и g :: c -> d (где я имею в виду, что c являются одним и тем же типом в обеих подписях, поэтому я не пишу здесь правильные подписи Haskell). Поскольку часто любят думать о f как о "функции двух переменных", можно ошибиться и подумать о g . f как о функции \x y -> g (f x y) (тип a -> b -> d). Это не так: см., например, этот вопрос и ответы на него или конкретный пример в этот ответ. Или еще лучше: посмотрите на тип (.) и сами определите, каким должен быть g . f! (Подсказка: Каждая функция принимает ровно один аргумент. Что такое типа одного аргумента f?)

person gspr    schedule 15.11.2013
comment
См. здесь, чтобы сделать это: (p `isPrefixOf`) - person pat; 15.11.2013
comment
Отличные темы для обсуждения! Большое спасибо за исчерпывающий ответ. - person Jeff Maner; 15.11.2013