This scenario seems to happen to me all the time. In a main function, I do pattern matching (case ... of
) to identify what variant I have to deal with, to decide which subfunction to call. This subfunction should then be applied to only that variant. But I can’t find a way to define a function only on a variant. So if I write such a subfunction, unfortunately, I have to define it on the whole type, and match again to extract the variant (despite me and the compiler already knowing which variant it is).
Is there a solution here?
Could you share a snippet to illustrate the problem?
You can pass the values of the constructors to each subfunction:
https://ellie-app.com/fcS2tgmfp5xa1
If you need to ensure the function can only receive the specific variant, you can do this way:
https://ellie-app.com/fcS2sDJZ2Mma1
You have to remove the type constructor on the sub function. So if your type is
type Foo = Bar Int String | Baz (List String) Foo
you could do
handleFoo : Foo -> Float
handleFoo foo =
case foo of
Bar theInt theString ->
handleBar theInt theString
Baz theList theFoo ->
handleBaz theList theFoo
handleBar : Int -> String -> Float
handleBar theInt theString = ...
handleBaz : List String -> Foo -> Float
handleBaz theList theFoo = ...
(Though I’m usually happy just having a long function if it has a clear separation into cases; I would only bother with factoring out the cases like this if there was a good reason to do it. If I end up with a bunch of functions named handleBar
and handleBaz
, I don’t really think that’s an improvement. In particular, the update
function often works like that.)
Thanks for your replies.
Actually my variants “constructors” take records as arguments, like this:
type MyType
= Variant1 { field1 : List String, field2 : ..., fieldN: ...}
| Variant2 { field1 : List String, field2: .... FieldN+1: ...}
I mean, there are a lot of fields in common, and also some fields that are differents depending on the variant. It’s a bit heavy to have to repeat all the fields of the record to define the function, but I guess that’s the best solution.
In that case, you could do
type alias Variant1Record = { field1 : List String, … }
type alias Variant2Record = { field1 : List String, ... }
type MyType = Variant1 Variant1Record | Variant2 Variant2Record
handleMyType : MyType -> Int
handleMyType mt = case mt of
Variant1 rec -> handleVariant1 rec
Variant2 rec -> handleVariant2 rec
handleVariant1 : Variant1Record -> Int
handleVariant1 rec = …
This way, you only need to list the fields once.
Oh, and you can also look into Elm’s extensible records to maybe factor out the repeated fields.
Also, when there is a good reason for fields to be common to multiple variant, I found that it almost always makes the code clearer to extract those fields in common like so
type alias MyType =
{ common1 : ...
, common2 : ...
, specific : Specific
}
type Specific
= Variant1 { field1 : ..., field2 : ... }
| Variant2 { field3 : ... }
See also this post discussing how record and custom types can be modelled using math (its not hard):
Pulling out the common fields is akin to extracting a common factor from some expression. I tend to think of product-sum-product form with all common fields pulled to the outer product as what you are generally aiming for.
This is in product-sum-product form:
-- Outer product
type alias MyType =
{ common1 : ...
, common2 : ...
, specific : Specific
}
-- Sum
type Specific
= Variant1 { field1 : ..., field2 : ... } -- Inner Product
| Variant2 { field3 : ... } -- Inner Product
You can also write a function that works over some subset of the fields:
myFun : { a | field3 : String, field8 : Int } -> Something
Then any custom type variant with those fields can be passed to it (removing the constructor first of course).
type Specific
= Variant1 { field1 : ..., field2 : ..., field3: String, ..., field8: Int }
| Variant2 { field3 : String, ..., field8: Int }
| VariantX ...
fnSomeVariants : Specific -> Something
fnSomeVariants s =
case s of
Variant1 fields -> myFun fields
Variant2 fields -> myFun fields
_ -> DefaultSomething
Nice, I didn’t know you could do that.
This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.